Merge "Improve TextView.onMeasure() for multiline text."
diff --git a/Android.bp b/Android.bp
index eb35ffb..33acffa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -40,6 +40,7 @@
// needed by the device.
srcs: [
"core/proto/android/os/kernelwake.proto",
+ "core/proto/android/os/procrank.proto",
"core/proto/android/service/graphicsstats.proto",
],
shared: {
diff --git a/Android.mk b/Android.mk
index 0642129..452bbf2 100644
--- a/Android.mk
+++ b/Android.mk
@@ -602,8 +602,10 @@
LOCAL_NO_STANDARD_LIBRARIES := true
LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp bouncycastle ext
-LOCAL_STATIC_JAVA_LIBRARIES := \
- framework-protos \
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ framework-protos \
+ android.hidl.base-V1.0-java \
+ android.hardware.cas-V1.0-java \
android.hardware.health-V1.0-java-constants \
android.hardware.thermal-V1.0-java-constants \
android.hardware.tv.input-V1.0-java-constants \
@@ -613,8 +615,6 @@
android.hardware.vibrator-V1.1-java-constants \
android.hardware.wifi-V1.0-java-constants \
-include hardware/interfaces/cas/1.0/CasHal.mk
-
# Loaded with System.loadLibrary by android.view.textclassifier
LOCAL_REQUIRED_MODULES += libtextclassifier
diff --git a/api/current.txt b/api/current.txt
index 586637b..80b7282 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13829,6 +13829,7 @@
public class Typeface {
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+ method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
method public static android.graphics.Typeface createFromFile(java.io.File);
method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -22742,6 +22743,7 @@
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -51646,7 +51648,9 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public boolean isAllCaps();
method public boolean isCursorVisible();
+ method public boolean isElegantTextHeight();
method public boolean isInputMethodTarget();
method public boolean isSuggestionsEnabled();
method public boolean isTextSelectable();
diff --git a/api/system-current.txt b/api/system-current.txt
index 2be0361..e393ed2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14627,6 +14627,7 @@
public class Typeface {
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+ method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
method public static android.graphics.Typeface createFromFile(java.io.File);
method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -24688,6 +24689,7 @@
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -55733,7 +55735,9 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public boolean isAllCaps();
method public boolean isCursorVisible();
+ method public boolean isElegantTextHeight();
method public boolean isInputMethodTarget();
method public boolean isSuggestionsEnabled();
method public boolean isTextSelectable();
diff --git a/api/test-current.txt b/api/test-current.txt
index efe22a7..c5caf39 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13898,6 +13898,7 @@
public class Typeface {
method public static android.graphics.Typeface create(java.lang.String, int);
method public static android.graphics.Typeface create(android.graphics.Typeface, int);
+ method public static android.graphics.Typeface create(android.graphics.Typeface, int, boolean);
method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
method public static android.graphics.Typeface createFromFile(java.io.File);
method public static android.graphics.Typeface createFromFile(java.lang.String);
@@ -22878,6 +22879,7 @@
method public android.graphics.Bitmap getFrameAtTime(long, int);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
+ method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
method public void release();
method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
method public void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -52107,7 +52109,9 @@
method public android.graphics.Typeface getTypeface();
method public android.text.style.URLSpan[] getUrls();
method public boolean hasSelection();
+ method public boolean isAllCaps();
method public boolean isCursorVisible();
+ method public boolean isElegantTextHeight();
method public boolean isInputMethodTarget();
method public boolean isSuggestionsEnabled();
method public boolean isTextSelectable();
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index c6d83f5..b69ef1c 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -152,7 +152,7 @@
System.out.println(pretty);
} else {
if (results != null) {
- for (String key : results.keySet()) {
+ for (String key : sorted(results.keySet())) {
System.out.println(
"INSTRUMENTATION_RESULT: " + key + "=" + results.get(key));
}
@@ -163,6 +163,10 @@
@Override
public void onError(String errorText, boolean commandError) {
+ if (mRawMode) {
+ System.out.println("onError: commandError=" + commandError + " message="
+ + errorText);
+ }
// The regular BaseCommand error printing will print the commandErrors.
if (!commandError) {
System.out.println(errorText);
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 3ea823c..0532083 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -10,7 +10,7 @@
srcs: [
"IncidentHelper.cpp",
- "strutil.cpp",
+ "ih_util.cpp",
],
shared_libs: [
@@ -38,6 +38,7 @@
srcs: [
"tests/IncidentHelper_test.cpp",
+ "tests/ih_util_test.cpp",
],
data: [
diff --git a/cmds/incident_helper/IncidentHelper.cpp b/cmds/incident_helper/IncidentHelper.cpp
index 89d8947..aa06595 100644
--- a/cmds/incident_helper/IncidentHelper.cpp
+++ b/cmds/incident_helper/IncidentHelper.cpp
@@ -17,14 +17,13 @@
#define LOG_TAG "incident_helper"
#include "IncidentHelper.h"
-#include "strutil.h"
+#include "ih_util.h"
#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
-#include <algorithm>
#include <android-base/file.h>
#include <unistd.h>
-#include <sstream>
#include <string>
#include <vector>
@@ -67,42 +66,34 @@
const string KERNEL_WAKEUP_LINE_DELIMITER = "\t";
status_t KernelWakesParser::Parse(const int in, const int out) const {
- // read the content, this is not memory-efficient though since it loads everything
- // However the data will be held in proto anyway, and incident_helper is less critical
- string content;
- if (!ReadFdToString(in, &content)) {
- fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
- return -1;
- }
-
- istringstream iss(content);
+ Reader reader(in);
string line;
- vector<string> header; // the header of /d/wakeup_sources
- vector<string> record; // retain each record
+ header_t header; // the header of /d/wakeup_sources
+ record_t record; // retain each record
int nline = 0;
KernelWakeSources proto;
// parse line by line
- while (getline(iss, line)) {
+ while (reader.readLine(line)) {
+ if (line.empty()) continue;
// parse head line
- if (nline == 0) {
- split(line, &header);
- if (!assertHeaders(kernel_wake_headers, header)) {
- fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
- return BAD_VALUE;
- }
- nline++;
- continue;
+ if (nline++ == 0) {
+ split(line, header, KERNEL_WAKEUP_LINE_DELIMITER);
+ if (!assertHeaders(kernel_wake_headers, header)) {
+ fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+ return BAD_VALUE;
+ }
+ continue;
}
// parse for each record, the line delimiter is \t only!
- split(line, &record, KERNEL_WAKEUP_LINE_DELIMITER);
+ split(line, record, KERNEL_WAKEUP_LINE_DELIMITER);
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;
+ // 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;
}
WakeupSourceProto* source = proto.add_wakeup_sources();
@@ -118,17 +109,106 @@
source->set_max_time(atol(record.at(7).c_str()));
source->set_last_change(atol(record.at(8).c_str()));
source->set_prevent_suspend_time(atol(record.at(9).c_str()));
-
- nline++;
}
- fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+ if (!reader.ok(line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
if (!proto.SerializeToFileDescriptor(out)) {
fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
return -1;
}
- close(out);
-
+ fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
return NO_ERROR;
}
+
+// ================================================================================
+const char* procrank_headers[] = {
+ "PID", // id: 1
+ "Vss", // id: 2
+ "Rss", // id: 3
+ "Pss", // id: 4
+ "Uss", // id: 5
+ "Swap", // id: 6
+ "PSwap", // id: 7
+ "USwap", // id: 8
+ "ZSwap", // id: 9
+ "cmdline", // id: 10
+};
+
+status_t ProcrankParser::Parse(const int in, const int out) const {
+ Reader reader(in);
+ string line, content;
+ header_t header; // the header of /d/wakeup_sources
+ record_t record; // retain each record
+ int nline = 0;
+
+ Procrank proto;
+
+ // parse line by line
+ while (reader.readLine(line)) {
+ if (line.empty()) continue;
+
+ // parse head line
+ if (nline++ == 0) {
+ split(line, header);
+ if (!assertHeaders(procrank_headers, header)) {
+ fprintf(stderr, "[%s]Bad header:\n%s\n", this->name.string(), line.c_str());
+ return BAD_VALUE;
+ }
+ continue;
+ }
+
+ split(line, record);
+ if (record.size() != header.size()) {
+ if (record[record.size() - 1] == "TOTAL") { // TOTAL record
+ ProcessProto* total = proto.mutable_summary()->mutable_total();
+ total->set_pss(atol(record.at(0).substr(0, record.at(0).size() - 1).c_str()));
+ total->set_uss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+ total->set_swap(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+ total->set_pswap(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+ total->set_uswap(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+ total->set_zswap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+ } else if (record[0] == "ZRAM:") {
+ split(line, record, ":");
+ proto.mutable_summary()->mutable_zram()->set_raw_text(record[1]);
+ } else if (record[0] == "RAM:") {
+ split(line, record, ":");
+ proto.mutable_summary()->mutable_ram()->set_raw_text(record[1]);
+ } else {
+ fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+ line.c_str());
+ }
+ continue;
+ }
+
+ ProcessProto* process = proto.add_processes();
+ // int32
+ process->set_pid(atoi(record.at(0).c_str()));
+ // int64, remove 'K' at the end
+ process->set_vss(atol(record.at(1).substr(0, record.at(1).size() - 1).c_str()));
+ process->set_rss(atol(record.at(2).substr(0, record.at(2).size() - 1).c_str()));
+ process->set_pss(atol(record.at(3).substr(0, record.at(3).size() - 1).c_str()));
+ process->set_uss(atol(record.at(4).substr(0, record.at(4).size() - 1).c_str()));
+ process->set_swap(atol(record.at(5).substr(0, record.at(5).size() - 1).c_str()));
+ process->set_pswap(atol(record.at(6).substr(0, record.at(6).size() - 1).c_str()));
+ process->set_uswap(atol(record.at(7).substr(0, record.at(7).size() - 1).c_str()));
+ process->set_zswap(atol(record.at(8).substr(0, record.at(8).size() - 1).c_str()));
+ // string
+ process->set_cmdline(record.at(9));
+ }
+
+ if (!reader.ok(line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.SerializeToFileDescriptor(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %d bytes\n", this->name.string(), proto.ByteSize());
+ return NO_ERROR;
+}
\ No newline at end of file
diff --git a/cmds/incident_helper/IncidentHelper.h b/cmds/incident_helper/IncidentHelper.h
index 40792a8..736f848 100644
--- a/cmds/incident_helper/IncidentHelper.h
+++ b/cmds/incident_helper/IncidentHelper.h
@@ -70,4 +70,17 @@
virtual status_t Parse(const int in, const int out) const;
};
+/**
+ * Procrank parser, parses text produced by command procrank
+ */
+extern const char* procrank_headers[];
+
+class ProcrankParser : public TextParserBase {
+public:
+ ProcrankParser() : TextParserBase(String8("ProcrankParser")) {};
+ ~ProcrankParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
#endif // INCIDENT_HELPER_H
diff --git a/cmds/incident_helper/README.md b/cmds/incident_helper/README.md
index 967f1e3..866cc20 100644
--- a/cmds/incident_helper/README.md
+++ b/cmds/incident_helper/README.md
@@ -1,5 +1,7 @@
# incident_helper
+It is an executable used to help parsing text format data to protobuf.
+
## How to build, deploy, unit test
For the first time, build the test and create an empty directly on device:
@@ -15,3 +17,10 @@
adb push $OUT/data/nativetest64/incident_helper_test/* /data/nativetest64/incident_helper_test/ && \
adb shell /data/nativetest64/incident_helper_test/incident_helper_test 2>/dev/null
```
+## How to adapt proto changes
+
+If add a new proto file, add it in Android.bp under frameworks/base/ and make incident helper
+
+```
+root$ make -j48 incident_helper
+```
diff --git a/cmds/incident_helper/ih_util.cpp b/cmds/incident_helper/ih_util.cpp
new file mode 100644
index 0000000..bbb625f
--- /dev/null
+++ b/cmds/incident_helper/ih_util.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 "ih_util.h"
+
+#include <sstream>
+#include <unistd.h>
+
+const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB
+
+std::string trim(const std::string& s, const std::string& whitespace) {
+ const auto head = s.find_first_not_of(whitespace);
+ if (head == std::string::npos) return "";
+
+ const auto tail = s.find_last_not_of(whitespace);
+ return s.substr(head, tail - head + 1);
+}
+
+// This is similiar to Split in android-base/file.h, but it won't add empty string
+void split(const std::string& line, std::vector<std::string>& words, const std::string& delimiters) {
+ words.clear(); // clear the buffer before split
+
+ size_t base = 0;
+ size_t found;
+ while (true) {
+ found = line.find_first_of(delimiters, base);
+ if (found != base) {
+ std::string word = trim(line.substr(base, found - base));
+ if (!word.empty()) {
+ words.push_back(word);
+ }
+ }
+ if (found == line.npos) break;
+ base = found + 1;
+ }
+}
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
+ for (size_t i = 0; i < actual.size(); i++) {
+ if (expected[i] == NULL || std::string(expected[i]) != actual[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
+
+Reader::Reader(const int fd, const size_t capacity)
+ : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
+{
+ mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
+ mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
+}
+
+Reader::~Reader()
+{
+ free(mBuf);
+}
+
+bool Reader::readLine(std::string& line, const char newline) {
+ if (!ok(line)) return false; // bad status
+ std::stringstream ss;
+ while (!EOR()) {
+ // read if available
+ if (mFd != -1 && mBufSize != mMaxSize) {
+ ssize_t amt = 0;
+ if (mRead >= mFlushed) {
+ amt = ::read(mFd, mBuf + mRead, mMaxSize - mRead);
+ } else {
+ amt = ::read(mFd, mBuf + mRead, mFlushed - mRead);
+ }
+ if (amt < 0) {
+ mStatus = "Fail to read from fd";
+ return false;
+ } else if (amt == 0) {
+ close(mFd);
+ mFd = -1;
+ }
+ mRead += amt;
+ mBufSize += amt;
+ }
+
+ bool meetsNewLine = false;
+ if (mBufSize > 0) {
+ int start = mFlushed;
+ int end = mFlushed < mRead ? mRead : mMaxSize;
+ while (mFlushed < end && mBuf[mFlushed++] != newline && mBufSize > 0) mBufSize--;
+ meetsNewLine = (mBuf[mFlushed-1] == newline);
+ if (meetsNewLine) mBufSize--; // deduct the new line character
+ size_t len = meetsNewLine ? mFlushed - start - 1 : mFlushed - start;
+ ss.write(mBuf + start, len);
+ }
+
+ if (mRead >= (int) mMaxSize) mRead = 0;
+ if (mFlushed >= (int) mMaxSize) mFlushed = 0;
+
+ if (EOR() || meetsNewLine) {
+ line.assign(ss.str());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Reader::ok(std::string& error) {
+ error.assign(mStatus);
+ return mStatus.empty();
+}
diff --git a/cmds/incident_helper/ih_util.h b/cmds/incident_helper/ih_util.h
new file mode 100644
index 0000000..9e0c18e
--- /dev/null
+++ b/cmds/incident_helper/ih_util.h
@@ -0,0 +1,64 @@
+/*
+ * 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 INCIDENT_HELPER_UTIL_H
+#define INCIDENT_HELPER_UTIL_H
+
+#include <string>
+#include <vector>
+#include <sstream>
+
+typedef std::vector<std::string> header_t;
+typedef std::vector<std::string> record_t;
+
+const char DEFAULT_NEWLINE = '\n';
+const std::string DEFAULT_WHITESPACE = " \t";
+
+std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
+
+void split(const std::string& line, std::vector<std::string>& words,
+ const std::string& delimiters = DEFAULT_WHITESPACE);
+
+bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
+
+/**
+ * Reader class reads data from given fd in streaming fashion.
+ * The buffer size is controlled by capacity parameter.
+ */
+class Reader
+{
+public:
+ Reader(const int fd);
+ Reader(const int fd, const size_t capacity);
+ ~Reader();
+
+ bool readLine(std::string& line, const char newline = DEFAULT_NEWLINE);
+ bool ok(std::string& error);
+
+private:
+ int mFd; // set mFd to -1 when read EOF()
+ const size_t mMaxSize;
+ size_t mBufSize;
+ char* mBuf; // implements a circular buffer
+
+ int mRead;
+ int mFlushed;
+ std::string mStatus;
+ // end of read
+ inline bool EOR() { return mFd == -1 && mBufSize == 0; };
+};
+
+#endif // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/main.cpp b/cmds/incident_helper/main.cpp
index 1e25c85..333344b 100644
--- a/cmds/incident_helper/main.cpp
+++ b/cmds/incident_helper/main.cpp
@@ -27,12 +27,11 @@
using namespace std;
static void usage(FILE* out) {
- fprintf(out, "incident_helper is not designed to run manually, see README.md\n");
- fprintf(out, "usage: incident_helper -s SECTION -i INPUT -o OUTPUT\n");
+ fprintf(out, "incident_helper is not designed to run manually,");
+ fprintf(out, "it reads from stdin and writes to stdout, see README.md for details.\n");
+ fprintf(out, "usage: incident_helper -s SECTION\n");
fprintf(out, "REQUIRED:\n");
fprintf(out, " -s section id, must be positive\n");
- fprintf(out, " -i (default stdin) input fd\n");
- fprintf(out, " -o (default stdout) output fd\n");
}
//=============================================================================
@@ -45,6 +44,8 @@
return new ReverseParser();
/* ========================================================================= */
// IDs larger than 0 are reserved in incident.proto
+ case 2000:
+ return new ProcrankParser();
case 2002:
return new KernelWakesParser();
default:
@@ -59,9 +60,7 @@
// Parse the args
int opt;
int sectionID = 0;
- int inputFd = STDIN_FILENO;
- int outputFd = STDOUT_FILENO;
- while ((opt = getopt(argc, argv, "hs:i:o:")) != -1) {
+ while ((opt = getopt(argc, argv, "hs:")) != -1) {
switch (opt) {
case 'h':
usage(stdout);
@@ -69,30 +68,14 @@
case 's':
sectionID = atoi(optarg);
break;
- case 'i':
- inputFd = atoi(optarg);
- break;
- case 'o':
- outputFd = atoi(optarg);
- break;
}
}
- // Check mandatory parameters:
- if (inputFd < 0) {
- fprintf(stderr, "invalid input fd: %d\n", inputFd);
- return 1;
- }
- if (outputFd < 0) {
- fprintf(stderr, "invalid output fd: %d\n", outputFd);
- return 1;
- }
-
fprintf(stderr, "Pasring section %d...\n", sectionID);
TextParserBase* parser = selectParser(sectionID);
if (parser != NULL) {
fprintf(stderr, "Running parser: %s\n", parser->name.string());
- status_t err = parser->Parse(inputFd, outputFd);
+ status_t err = parser->Parse(STDIN_FILENO, STDOUT_FILENO);
if (err != NO_ERROR) {
fprintf(stderr, "Parse error in section %d: %s\n", sectionID, strerror(-err));
return -1;
diff --git a/cmds/incident_helper/strutil.cpp b/cmds/incident_helper/strutil.cpp
deleted file mode 100644
index 21b04a1..0000000
--- a/cmds/incident_helper/strutil.cpp
+++ /dev/null
@@ -1,55 +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.
- */
-
-#define LOG_TAG "incident_helper"
-
-#include "strutil.h"
-
-#include <sstream>
-
-std::string trim(const std::string& s, const std::string& whitespace) {
- const auto head = s.find_first_not_of(whitespace);
- if (head == std::string::npos) return "";
-
- const auto tail = s.find_last_not_of(whitespace);
- return s.substr(head, tail - head + 1);
-}
-
-// This is similiar to Split in android-base/file.h, but it won't add empty string
-void split(const std::string& line, std::vector<std::string>* words, const std::string& delimiters) {
- words->clear(); // clear the buffer before split
-
- size_t base = 0;
- size_t found;
- while (true) {
- found = line.find_first_of(delimiters, base);
- if (found != base) { // ignore empty string
- // one char before found
- words->push_back(line.substr(base, found - base));
- }
- if (found == line.npos) break;
- base = found + 1;
- }
-}
-
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual) {
- for (size_t i = 0; i < actual.size(); i++) {
- if (expected[i] == NULL || std::string(expected[i]) != actual.at(i)) {
- return false;
- }
- }
- return true;
-}
diff --git a/cmds/incident_helper/strutil.h b/cmds/incident_helper/strutil.h
deleted file mode 100644
index fcc164d..0000000
--- a/cmds/incident_helper/strutil.h
+++ /dev/null
@@ -1,30 +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.
- */
-
-#ifndef STRUTIL_H
-#define STRUTIL_H
-
-#include <string>
-#include <vector>
-
-const std::string DEFAULT_WHITESPACE = " \t";
-
-std::string trim(const std::string& s, const std::string& whitespace = DEFAULT_WHITESPACE);
-void split(const std::string& line, std::vector<std::string>* words,
- const std::string& delimiters = DEFAULT_WHITESPACE);
-bool assertHeaders(const char* expected[], const std::vector<std::string>& actual);
-
-#endif // STRUTIL_H
diff --git a/cmds/incident_helper/testdata/procrank.txt b/cmds/incident_helper/testdata/procrank.txt
new file mode 100644
index 0000000..5d2d8d2
--- /dev/null
+++ b/cmds/incident_helper/testdata/procrank.txt
@@ -0,0 +1,8 @@
+ PID Vss Rss Pss Uss Swap PSwap USwap ZSwap cmdline
+ 1119 2607640K 339564K 180278K 114216K 1584K 46K 0K 10K system_server
+ 649 11016K 1448K 98K 48K 472K 342K 212K 75K /vendor/bin/qseecomd
+ ------ ------ ------ ------ ------ ------ ------
+ 1201993K 935300K 88164K 31069K 27612K 6826K TOTAL
+
+ZRAM: 6828K physical used for 31076K in swap (524284K total swap)
+ RAM: 3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/IncidentHelper_test.cpp b/cmds/incident_helper/tests/IncidentHelper_test.cpp
index 9d5d9e4..ac3e84d 100644
--- a/cmds/incident_helper/tests/IncidentHelper_test.cpp
+++ b/cmds/incident_helper/tests/IncidentHelper_test.cpp
@@ -17,10 +17,12 @@
#include "IncidentHelper.h"
#include "frameworks/base/core/proto/android/os/kernelwake.pb.h"
+#include "frameworks/base/core/proto/android/os/procrank.pb.h"
#include <android-base/file.h>
#include <android-base/test_utils.h>
#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
#include <gtest/gtest.h>
#include <string.h>
#include <fcntl.h>
@@ -37,10 +39,19 @@
class IncidentHelperTest : public Test {
public:
virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+ std::string getSerializedString(::google::protobuf::Message& message) {
+ std::string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
}
protected:
+ TemporaryFile tf;
+
const std::string kTestPath = GetExecutableDirectory();
const std::string kTestDataPath = kTestPath + "/testdata/";
};
@@ -61,9 +72,6 @@
const std::string testFile = kTestDataPath + "kernel_wakeups.txt";
KernelWakesParser parser;
KernelWakeSources expected;
- std::string expectedStr;
- TemporaryFile tf;
- ASSERT_TRUE(tf.fd != -1);
WakeupSourceProto* record1 = expected.add_wakeup_sources();
record1->set_name("ipc000000ab_ATFWD-daemon");
@@ -89,15 +97,12 @@
record2->set_last_change(2067286206l);
record2->set_prevent_suspend_time(0l);
- ASSERT_TRUE(expected.SerializeToFileDescriptor(tf.fd));
- ASSERT_TRUE(ReadFileToString(tf.path, &expectedStr));
-
int fd = open(testFile.c_str(), O_RDONLY, 0444);
ASSERT_TRUE(fd != -1);
CaptureStdout();
ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
- EXPECT_EQ(GetCapturedStdout(), expectedStr);
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
close(fd);
}
@@ -115,3 +120,54 @@
EXPECT_THAT(GetCapturedStderr(), StrEq("[KernelWakeSources]Bad header:\nTHIS IS BAD HEADER\n"));
close(fd);
}
+
+TEST_F(IncidentHelperTest, ProcrankParser) {
+ const std::string testFile = kTestDataPath + "procrank.txt";
+ ProcrankParser parser;
+ Procrank expected;
+
+ ProcessProto* process1 = expected.add_processes();
+ process1->set_pid(1119);
+ process1->set_vss(2607640);
+ process1->set_rss(339564);
+ process1->set_pss(180278);
+ process1->set_uss(114216);
+ process1->set_swap(1584);
+ process1->set_pswap(46);
+ process1->set_uswap(0);
+ process1->set_zswap(10);
+ process1->set_cmdline("system_server");
+
+ ProcessProto* process2 = expected.add_processes();
+ process2->set_pid(649);
+ process2->set_vss(11016);
+ process2->set_rss(1448);
+ process2->set_pss(98);
+ process2->set_uss(48);
+ process2->set_swap(472);
+ process2->set_pswap(342);
+ process2->set_uswap(212);
+ process2->set_zswap(75);
+ process2->set_cmdline("/vendor/bin/qseecomd");
+
+ ProcessProto* total = expected.mutable_summary()->mutable_total();
+ total->set_pss(1201993);
+ total->set_uss(935300);
+ total->set_swap(88164);
+ total->set_pswap(31069);
+ total->set_uswap(27612);
+ total->set_zswap(6826);
+
+ expected.mutable_summary()->mutable_zram()
+ ->set_raw_text("6828K physical used for 31076K in swap (524284K total swap)");
+ expected.mutable_summary()->mutable_ram()
+ ->set_raw_text("3843972K total, 281424K free, 116764K buffers, 1777452K cached, 1136K shmem, 217916K slab");
+
+ int fd = open(testFile.c_str(), O_RDONLY, 0444);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
new file mode 100644
index 0000000..5158e0a
--- /dev/null
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "ih_util.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+
+TEST(IhUtilTest, Trim) {
+ EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw "), StrEq("100 00\toooh \t wqrw"));
+ EXPECT_THAT(trim(" \t 100 00\toooh \t wqrw ", " "), StrEq("\t 100 00\toooh \t wqrw"));
+}
+
+TEST(IhUtilTest, Split) {
+ vector<string> result, expected;
+ split(" \t \t\t ", result);
+ EXPECT_EQ(expected, result);
+
+ split(" \t 100 00\toooh \t wqrw", result);
+ expected = { "100", "00", "oooh", "wqrw" };
+ EXPECT_EQ(expected, result);
+
+ split(" \t 100 00\toooh \t wqrw", result, "\t");
+ expected = { "100 00", "oooh", "wqrw" };
+ EXPECT_EQ(expected, result);
+
+ split("123,456,78_9", result, ",");
+ expected = { "123", "456", "78_9" };
+ EXPECT_EQ(expected, result);
+}
+
+TEST(IhUtilTest, Reader) {
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1);
+ ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+
+ Reader r(tf.fd);
+ string line;
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("test string"));
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("second"));
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("ooo"));
+ ASSERT_FALSE(r.readLine(line));
+ ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderSmallBufSize) {
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1);
+ ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
+
+ Reader r(tf.fd, 5);
+ string line;
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("test string"));
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("second"));
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq("ooiecccojreo"));
+ ASSERT_FALSE(r.readLine(line));
+ ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderEmpty) {
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1);
+ ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+
+ Reader r(tf.fd);
+ string line;
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq(""));
+ ASSERT_FALSE(r.readLine(line));
+ ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderMultipleEmptyLines) {
+ TemporaryFile tf;
+ ASSERT_NE(tf.fd, -1);
+ ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+
+ Reader r(tf.fd);
+ string line;
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq(""));
+ ASSERT_TRUE(r.readLine(line));
+ EXPECT_THAT(line, StrEq(""));
+ ASSERT_FALSE(r.readLine(line));
+ EXPECT_THAT(line, StrEq(""));
+ ASSERT_TRUE(r.ok(line));
+}
+
+TEST(IhUtilTest, ReaderFailedNegativeFd) {
+ Reader r(-123);
+ string line;
+ EXPECT_FALSE(r.readLine(line));
+ EXPECT_FALSE(r.ok(line));
+ EXPECT_THAT(line, StrEq("Negative fd"));
+}
+
+TEST(IhUtilTest, ReaderFailedZeroBufferSize) {
+ Reader r(23, 0);
+ string line;
+ EXPECT_FALSE(r.readLine(line));
+ EXPECT_FALSE(r.ok(line));
+ EXPECT_THAT(line, StrEq("Zero buffer capacity"));
+}
+
+TEST(IhUtilTest, ReaderFailedBadFd) {
+ Reader r(1231432);
+ string line;
+ EXPECT_FALSE(r.readLine(line));
+ EXPECT_FALSE(r.ok(line));
+ EXPECT_THAT(line, StrEq("Fail to read from fd"));
+}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index a8a5483..537c910 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -79,7 +79,10 @@
src/Reporter.cpp \
src/Section.cpp \
src/protobuf.cpp \
+ src/report_directory.cpp \
+ src/section_list.cpp \
tests/FdBuffer_test.cpp \
+ tests/Reporter_test.cpp \
tests/Section_test.cpp \
LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
index d11e3cf..250a7f3 100644
--- a/cmds/incidentd/incidentd.rc
+++ b/cmds/incidentd/incidentd.rc
@@ -12,5 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-#service incidentd /system/bin/incidentd
+# service incidentd /system/bin/incidentd
# class main
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index 7888442..03a6d18 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -98,7 +98,7 @@
bool close() { return !(::close(mFds[0]) || ::close(mFds[1])); }
~Fpipe() { close(); }
- inline status_t init() { return pipe(mFds); }
+ inline bool init() { return pipe(mFds) != -1; }
inline int readFd() const { return mFds[0]; }
inline int writeFd() const { return mFds[1]; }
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 7c6789e..c4b54bb 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -153,7 +153,6 @@
break;
}
reporter->batch.add(request);
- reporter->args.merge(request->args);
}
// Take the report, which might take a while. More requests might queue
@@ -235,7 +234,7 @@
return Status::fromExceptionCode(Status::EX_SECURITY,
"Only system uid can call systemRunning");
}
-
+
// When system_server is up and running, schedule the dropbox task to run.
mHandler->scheduleSendBacklogToDropbox();
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index ba157de6..ea73bcd 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -35,7 +35,7 @@
/**
* The directory where the incident reports are stored.
*/
-static const String8 INCIDENT_DIRECTORY("/data/incidents");
+static const char* INCIDENT_DIRECTORY = "/data/misc/incidents/";
// ================================================================================
static status_t write_all(int fd, uint8_t const* buf, size_t size)
@@ -68,6 +68,7 @@
// ================================================================================
ReportRequestSet::ReportRequestSet()
:mRequests(),
+ mSections(),
mWritableCount(0),
mMainFd(-1)
{
@@ -77,10 +78,12 @@
{
}
+// TODO: dedup on exact same args and fd, report the status back to listener!
void
ReportRequestSet::add(const sp<ReportRequest>& request)
{
mRequests.push_back(request);
+ mSections.merge(request->args);
mWritableCount++;
}
@@ -122,11 +125,16 @@
return mWritableCount > 0 ? NO_ERROR : err;
}
+bool
+ReportRequestSet::containsSection(int id) {
+ return mSections.containsSection(id);
+}
// ================================================================================
-Reporter::Reporter()
- :args(),
- batch()
+Reporter::Reporter() : Reporter(INCIDENT_DIRECTORY) { isTest = false; };
+
+Reporter::Reporter(const char* directory)
+ :batch()
{
char buf[100];
@@ -134,10 +142,15 @@
mMaxSize = 100 * 1024 * 1024;
mMaxCount = 100;
+ // string ends up with '/' is a directory
+ String8 dir = String8(directory);
+ if (directory[dir.size() - 1] != '/') dir += "/";
+ mIncidentDirectory = dir.string();
+
// There can't be two at the same time because it's on one thread.
mStartTime = time(NULL);
- strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
- mFilename = INCIDENT_DIRECTORY + buf;
+ strftime(buf, sizeof(buf), "incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
+ mFilename = mIncidentDirectory + buf;
}
Reporter::~Reporter()
@@ -161,7 +174,7 @@
}
if (needMainFd) {
// Create the directory
- err = create_directory(INCIDENT_DIRECTORY);
+ if (!isTest) err = create_directory(mIncidentDirectory);
if (err != NO_ERROR) {
goto done;
}
@@ -169,7 +182,7 @@
// If there are too many files in the directory (for whatever reason),
// delete the oldest ones until it's under the limit. Doing this first
// does mean that we can go over, so the max size is not a hard limit.
- clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount);
+ if (!isTest) clean_directory(mIncidentDirectory, mMaxSize, mMaxCount);
// Open the file.
err = create_file(&mainFd);
@@ -214,7 +227,7 @@
const int id = (*section)->id;
ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string());
- if (this->args.containsSection(id)) {
+ if (this->batch.containsSection(id)) {
// Notify listener of starting
for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
@@ -270,7 +283,7 @@
// If the status was ok, delete the file. If not, leave it around until the next
// boot or the next checkin. If the directory gets too big older files will
// be rotated out.
- unlink(mFilename.c_str());
+ if(!isTest) unlink(mFilename.c_str());
}
return REPORT_FINISHED;
@@ -284,7 +297,7 @@
{
const char* filename = mFilename.c_str();
- *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660);
+ *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR | O_CLOEXEC, 0660);
if (*fd < 0) {
ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
return -errno;
@@ -303,20 +316,24 @@
return NO_ERROR;
}
-// ================================================================================
Reporter::run_report_status_t
Reporter::upload_backlog()
{
DIR* dir;
struct dirent* entry;
struct stat st;
+ status_t err;
- if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) {
- ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string());
+ if ((err = create_directory(INCIDENT_DIRECTORY)) != NO_ERROR) {
+ ALOGE("directory doesn't exist: %s", strerror(-err));
+ return REPORT_FINISHED;
+ }
+
+ if ((dir = opendir(INCIDENT_DIRECTORY)) == NULL) {
+ ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY);
return REPORT_NEEDS_DROPBOX;
}
- String8 dirbase(INCIDENT_DIRECTORY + "/");
sp<DropBoxManager> dropbox = new DropBoxManager();
// Enumerate, count and add up size
@@ -324,7 +341,7 @@
if (entry->d_name[0] == '.') {
continue;
}
- String8 filename = dirbase + entry->d_name;
+ String8 filename = String8(INCIDENT_DIRECTORY) + entry->d_name;
if (stat(filename.string(), &st) != 0) {
ALOGE("Unable to stat file %s", filename.string());
continue;
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index 5b86561..509611c 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -62,8 +62,10 @@
iterator begin() { return mRequests.begin(); }
iterator end() { return mRequests.end(); }
+ bool containsSection(int id);
private:
vector<sp<ReportRequest>> mRequests;
+ IncidentReportArgs mSections;
int mWritableCount;
int mMainFd;
};
@@ -77,10 +79,10 @@
REPORT_NEEDS_DROPBOX = 1
};
- IncidentReportArgs args;
ReportRequestSet batch;
Reporter();
+ Reporter(const char* directory);
virtual ~Reporter();
// Run the report as described in the batch and args parameters.
@@ -89,12 +91,16 @@
static run_report_status_t upload_backlog();
private:
+ String8 mIncidentDirectory;
+
string mFilename;
off_t mMaxSize;
size_t mMaxCount;
time_t mStartTime;
status_t create_file(int* fd);
+
+ bool isTest = true; // default to true for testing
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index ddb54c1..9cc47ed 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -19,6 +19,7 @@
#include "Section.h"
#include "protobuf.h"
+#include <private/android_filesystem_config.h>
#include <binder/IServiceManager.h>
#include <mutex>
#include <stdio.h>
@@ -29,15 +30,68 @@
using namespace std;
-const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
-const int64_t INCIDENT_HELPER_TIMEOUT_MS = 5 * 1000; // 5 seconds
+const int WAIT_MAX = 5;
+const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
const char* INCIDENT_HELPER = "/system/bin/incident_helper";
-const uid_t IH_UID = 9999; // run incident_helper as nobody
-const gid_t IH_GID = 9999;
+
+static pid_t
+forkAndExecuteIncidentHelper(const int id, const char* name, Fpipe& p2cPipe, Fpipe& c2pPipe)
+{
+ const char* ihArgs[] { INCIDENT_HELPER, "-s", to_string(id).c_str(), NULL };
+
+ // fork used in multithreaded environment, avoid adding unnecessary code in child process
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child process executes incident helper as nobody
+ if (setgid(AID_NOBODY) == -1) {
+ ALOGW("%s can't change gid: %s", name, strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+ if (setuid(AID_NOBODY) == -1) {
+ ALOGW("%s can't change uid: %s", name, strerror(errno));
+ _exit(EXIT_FAILURE);
+ }
+
+ if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close() ||
+ dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
+ ALOGW("%s can't setup stdin and stdout for incident helper", name);
+ _exit(EXIT_FAILURE);
+ }
+
+ execv(INCIDENT_HELPER, const_cast<char**>(ihArgs));
+
+ ALOGW("%s failed in incident helper process: %s", name, strerror(errno));
+ _exit(EXIT_FAILURE); // always exits with failure if any
+ }
+ // close the fds used in incident helper
+ close(p2cPipe.readFd());
+ close(c2pPipe.writeFd());
+ return pid;
+}
+
+static status_t killChild(pid_t pid) {
+ int status;
+ kill(pid, SIGKILL);
+ if (waitpid(pid, &status, 0) == -1) return -1;
+ return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
+
+static status_t waitForChild(pid_t pid) {
+ int status;
+ bool died = false;
+ // wait for child to report status up to 1 seconds
+ for(int loop = 0; !died && loop < WAIT_MAX; loop++) {
+ if (waitpid(pid, &status, WNOHANG) == pid) died = true;
+ // sleep for 0.2 second
+ nanosleep(&WAIT_INTERVAL_NS, NULL);
+ }
+ if (!died) return killChild(pid);
+ return WIFEXITED(status) == 0 ? NO_ERROR : -WEXITSTATUS(status);
+}
// ================================================================================
-Section::Section(int i)
- :id(i)
+Section::Section(int i, const int64_t timeoutMs)
+ :id(i), timeoutMs(timeoutMs)
{
}
@@ -55,106 +109,57 @@
}
// ================================================================================
-FileSection::FileSection(int id, const char* filename)
- : Section(id), mFilename(filename) {
- name = "cat ";
- name += filename;
+FileSection::FileSection(int id, const char* filename, const int64_t timeoutMs)
+ : Section(id, timeoutMs), mFilename(filename) {
+ name = filename;
}
FileSection::~FileSection() {}
status_t FileSection::Execute(ReportRequestSet* requests) const {
- Fpipe p2cPipe;
- Fpipe c2pPipe;
- FdBuffer buffer;
-
- // initiate pipes to pass data to/from incident_helper
- if (p2cPipe.init() == -1) {
- return -errno;
- }
- if (c2pPipe.init() == -1) {
- return -errno;
- }
-
- // fork a child process
- pid_t pid = fork();
-
- if (pid == -1) {
- ALOGW("FileSection '%s' failed to fork", this->name.string());
- return -errno;
- }
-
- // child process
- if (pid == 0) {
- if (setgid(IH_GID) == -1) {
- ALOGW("FileSection '%s' can't change gid: %s", this->name.string(), strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (setuid(IH_UID) == -1) {
- ALOGW("FileSection '%s' can't change uid: %s", this->name.string(), strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- if (dup2(p2cPipe.readFd(), STDIN_FILENO) != 0 || !p2cPipe.close()) {
- ALOGW("FileSection '%s' failed to set up stdin: %s", this->name.string(), strerror(errno));
- exit(EXIT_FAILURE);
- }
- if (dup2(c2pPipe.writeFd(), STDOUT_FILENO) != 1 || !c2pPipe.close()) {
- ALOGW("FileSection '%s' failed to set up stdout: %s", this->name.string(), strerror(errno));
- exit(EXIT_FAILURE);
- }
-
- // execute incident_helper to parse raw file data and generate protobuf
- char sectionID[8]; // section id is expected to be smaller than 8 digits
- sprintf(sectionID, "%d", this->id);
- const char* args[]{INCIDENT_HELPER, "-s", sectionID, NULL};
- execv(INCIDENT_HELPER, const_cast<char**>(args));
-
- ALOGW("FileSection '%s' failed in child process: %s", this->name.string(), strerror(errno));
- return -1;
- }
-
- // parent process
-
- // close fds used in child process
- close(p2cPipe.readFd());
- close(c2pPipe.writeFd());
-
- // read from mFilename and pump buffer to incident_helper
- status_t err = NO_ERROR;
- int fd = open(mFilename, O_RDONLY);
+ // read from mFilename first, make sure the file is available
+ // add O_CLOEXEC to make sure it is closed when exec incident helper
+ int fd = open(mFilename, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
ALOGW("FileSection '%s' failed to open file", this->name.string());
return -errno;
}
- err = buffer.readProcessedDataInStream(fd,
- p2cPipe.writeFd(), c2pPipe.readFd(), INCIDENT_HELPER_TIMEOUT_MS);
- if (err != NO_ERROR) {
- ALOGW("FileSection '%s' failed to read data from incident helper: %s",
- this->name.string(), strerror(-err));
- kill(pid, SIGKILL); // kill child process if meets error
- return err;
- }
-
- if (buffer.timedOut()) {
- ALOGW("FileSection '%s' timed out reading from incident helper!", this->name.string());
- kill(pid, SIGKILL); // kill the child process if timed out
- }
-
- // has to block here to reap child process
- int status;
- int w = waitpid(pid, &status, 0);
- if (w < 0 || status == -1) {
- ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-err));
+ FdBuffer buffer;
+ Fpipe p2cPipe;
+ Fpipe c2pPipe;
+ // initiate pipes to pass data to/from incident_helper
+ if (!p2cPipe.init() || !c2pPipe.init()) {
+ ALOGW("FileSection '%s' failed to setup pipes", this->name.string());
return -errno;
}
- // write parsed data to reporter
- ALOGD("section '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+ pid_t pid = forkAndExecuteIncidentHelper(this->id, this->name.string(), p2cPipe, c2pPipe);
+ if (pid == -1) {
+ ALOGW("FileSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+
+ // parent process
+ status_t readStatus = buffer.readProcessedDataInStream(fd, p2cPipe.writeFd(), c2pPipe.readFd(),
+ this->timeoutMs);
+ if (readStatus != NO_ERROR || buffer.timedOut()) {
+ ALOGW("FileSection '%s' failed to read data from incident helper: %s, timedout: %s, kill: %s",
+ this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+ strerror(-killChild(pid)));
+ return readStatus;
+ }
+
+ status_t ihStatus = waitForChild(pid);
+ if (ihStatus != NO_ERROR) {
+ ALOGW("FileSection '%s' abnormal child process: %s", this->name.string(), strerror(-ihStatus));
+ return ihStatus;
+ }
+
+ ALOGD("FileSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
(int)buffer.durationMs());
WriteHeader(requests, buffer.size());
- err = buffer.write(requests);
+ status_t err = buffer.write(requests);
if (err != NO_ERROR) {
ALOGW("FileSection '%s' failed writing: %s", this->name.string(), strerror(-err));
return err;
@@ -263,7 +268,7 @@
pthread_attr_destroy(&attr);
// Loop reading until either the timeout or the worker side is done (i.e. eof).
- err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS);
+ err = buffer.read(data->readFd(), this->timeoutMs);
if (err != NO_ERROR) {
// TODO: Log this error into the incident report.
ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(),
@@ -309,7 +314,7 @@
}
// Write the data that was collected
- ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
+ ALOGD("WorkerThreadSection '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
(int)buffer.durationMs());
WriteHeader(requests, buffer.size());
err = buffer.write(requests);
@@ -322,42 +327,116 @@
}
// ================================================================================
-CommandSection::CommandSection(int id, const char* first, ...)
- :Section(id)
+void CommandSection::init(const char* command, va_list args)
+{
+ va_list copied_args;
+ va_copy(copied_args, args);
+ int numOfArgs = 0;
+ while(va_arg(args, const char*) != NULL) {
+ numOfArgs++;
+ }
+
+ // allocate extra 1 for command and 1 for NULL terminator
+ mCommand = (const char**)malloc(sizeof(const char*) * (numOfArgs + 2));
+
+ mCommand[0] = command;
+ name = command;
+ for (int i=0; i<numOfArgs; i++) {
+ const char* arg = va_arg(copied_args, const char*);
+ mCommand[i+1] = arg;
+ name += " ";
+ name += arg;
+ }
+ mCommand[numOfArgs+1] = NULL;
+ va_end(copied_args);
+}
+
+CommandSection::CommandSection(int id, const int64_t timeoutMs, const char* command, ...)
+ : Section(id, timeoutMs)
{
va_list args;
- int count = 0;
-
- va_start(args, first);
- while (va_arg(args, const char*) != NULL) {
- count++;
- }
+ va_start(args, command);
+ init(command, args);
va_end(args);
+}
- mCommand = (const char**)malloc(sizeof(const char*) * count);
-
- mCommand[0] = first;
- name = first;
- name += " ";
- va_start(args, first);
- for (int i=0; i<count; i++) {
- const char* arg = va_arg(args, const char*);
- mCommand[i+1] = arg;
- if (arg != NULL) {
- name += va_arg(args, const char*);
- name += " ";
- }
- }
+CommandSection::CommandSection(int id, const char* command, ...)
+ : Section(id)
+{
+ va_list args;
+ va_start(args, command);
+ init(command, args);
va_end(args);
}
CommandSection::~CommandSection()
{
+ free(mCommand);
}
status_t
-CommandSection::Execute(ReportRequestSet* /*requests*/) const
+CommandSection::Execute(ReportRequestSet* requests) const
{
+ FdBuffer buffer;
+ Fpipe cmdPipe;
+ Fpipe ihPipe;
+
+ if (!cmdPipe.init() || !ihPipe.init()) {
+ ALOGW("CommandSection '%s' failed to setup pipes", this->name.string());
+ return -errno;
+ }
+
+ pid_t cmdPid = fork();
+ if (cmdPid == -1) {
+ ALOGW("CommandSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+ // child process to execute the command as root
+ if (cmdPid == 0) {
+ // replace command's stdout with ihPipe's write Fd
+ if (dup2(cmdPipe.writeFd(), STDOUT_FILENO) != 1 || !ihPipe.close() || !cmdPipe.close()) {
+ 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);
+ 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
+ }
+ pid_t ihPid = forkAndExecuteIncidentHelper(this->id, this->name.string(), cmdPipe, ihPipe);
+ if (ihPid == -1) {
+ ALOGW("CommandSection '%s' failed to fork", this->name.string());
+ return -errno;
+ }
+
+ close(cmdPipe.writeFd());
+ status_t readStatus = buffer.read(ihPipe.readFd(), this->timeoutMs);
+ if (readStatus != NO_ERROR || buffer.timedOut()) {
+ ALOGW("CommandSection '%s' failed to read data from incident helper: %s, "
+ "timedout: %s, kill command: %s, kill incident helper: %s",
+ this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false",
+ strerror(-killChild(cmdPid)), strerror(-killChild(ihPid)));
+ return readStatus;
+ }
+
+ // TODO: wait for command here has one trade-off: the failed status of command won't be detected until
+ // buffer timeout, but it has advatage on starting the data stream earlier.
+ status_t cmdStatus = waitForChild(cmdPid);
+ status_t ihStatus = waitForChild(ihPid);
+ if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
+ ALOGW("CommandSection '%s' abnormal child processes, return status: command: %s, incident helper: %s",
+ this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
+ return cmdStatus != NO_ERROR ? cmdStatus : ihStatus;
+ }
+
+ ALOGD("CommandSection '%s' wrote %zd bytes in %d ms", this->name.string(), buffer.size(),
+ (int)buffer.durationMs());
+ WriteHeader(requests, buffer.size());
+ status_t err = buffer.write(requests);
+ if (err != NO_ERROR) {
+ ALOGW("CommandSection '%s' failed writing: %s", this->name.string(), strerror(-err));
+ return err;
+ }
return NO_ERROR;
}
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index 35740e9..93b4848 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -19,22 +19,26 @@
#include "FdBuffer.h"
+#include <stdarg.h>
#include <utils/String8.h>
#include <utils/String16.h>
#include <utils/Vector.h>
using namespace android;
+const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
+
/**
* Base class for sections
*/
class Section
{
public:
- int id;
+ const int id;
+ const int64_t timeoutMs; // each section must have a timeout
String8 name;
- Section(int id);
+ Section(int id, const int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS);
virtual ~Section();
virtual status_t Execute(ReportRequestSet* requests) const = 0;
@@ -48,7 +52,7 @@
class FileSection : public Section
{
public:
- FileSection(int id, const char* filename);
+ FileSection(int id, const char* filename, const int64_t timeoutMs = 5000 /* 5 seconds */);
virtual ~FileSection();
virtual status_t Execute(ReportRequestSet* requests) const;
@@ -77,13 +81,18 @@
class CommandSection : public Section
{
public:
- CommandSection(int id, const char* first, ...);
+ CommandSection(int id, const int64_t timeoutMs, const char* command, ...);
+
+ CommandSection(int id, const char* command, ...);
+
virtual ~CommandSection();
virtual status_t Execute(ReportRequestSet* requests) const;
private:
const char** mCommand;
+
+ void init(const char* command, va_list args);
};
/**
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index f60b8ac..110902c 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -129,7 +129,8 @@
return;
}
- String8 dirbase(String8(directory) + "/");
+ String8 dirbase(directory);
+ if (directory[dirbase.size() - 1] != '/') dirbase += "/";
off_t totalSize = 0;
size_t totalCount = 0;
diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp
index 72c54d2..80ddb86 100644
--- a/cmds/incidentd/src/section_list.cpp
+++ b/cmds/incidentd/src/section_list.cpp
@@ -21,6 +21,7 @@
*/
const Section* SECTION_LIST[] = {
// Linux Services
+ new CommandSection(2000, "/system/xbin/procrank", NULL),
new FileSection(2002, "/d/wakeup_sources"),
// System Services
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index ca5eec8..ba8b77a 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -25,6 +25,7 @@
const int READ_TIMEOUT = 5 * 1000;
const int BUFFER_SIZE = 16 * 1024;
+const int QUICK_TIMEOUT_MS = 100;
const std::string HEAD = "[OK]";
using namespace android;
@@ -101,11 +102,11 @@
write(c2pPipe.writeFd(), "poo", 3);
sleep(1);
}
- exit(EXIT_FAILURE);
+ _exit(EXIT_FAILURE);
} else {
close(c2pPipe.writeFd());
- status_t status = buffer.read(c2pPipe.readFd(), 500);
+ status_t status = buffer.read(c2pPipe.readFd(), QUICK_TIMEOUT_MS);
ASSERT_EQ(NO_ERROR, status);
EXPECT_TRUE(buffer.timedOut());
@@ -129,7 +130,7 @@
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
// Must exit here otherwise the child process will continue executing the test binary.
- exit(EXIT_SUCCESS);
+ _exit(EXIT_SUCCESS);
} else {
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
@@ -161,7 +162,7 @@
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
// Must exit here otherwise the child process will continue executing the test binary.
- exit(EXIT_SUCCESS);
+ _exit(EXIT_SUCCESS);
} else {
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
@@ -186,7 +187,7 @@
ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
- exit(EXIT_SUCCESS);
+ _exit(EXIT_SUCCESS);
} else {
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
@@ -212,7 +213,7 @@
ASSERT_TRUE(DoDataStream(p2cPipe.readFd(), c2pPipe.writeFd()));
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
- exit(EXIT_SUCCESS);
+ _exit(EXIT_SUCCESS);
} else {
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
@@ -239,13 +240,13 @@
while (true) {
sleep(1);
}
- exit(EXIT_FAILURE);
+ _exit(EXIT_FAILURE);
} else {
close(p2cPipe.readFd());
close(c2pPipe.writeFd());
ASSERT_EQ(NO_ERROR, buffer.readProcessedDataInStream(tf.fd,
- p2cPipe.writeFd(), c2pPipe.readFd(), 100));
+ p2cPipe.writeFd(), c2pPipe.readFd(), QUICK_TIMEOUT_MS));
EXPECT_TRUE(buffer.timedOut());
kill(pid, SIGKILL); // reap the child process
}
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
new file mode 100644
index 0000000..a774741
--- /dev/null
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -0,0 +1,197 @@
+// 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 "incidentd"
+
+#include "Reporter.h"
+
+#include <android/os/BnIncidentReportStatusListener.h>
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <dirent.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+class TestListener : public IIncidentReportStatusListener
+{
+public:
+ int startInvoked;
+ int finishInvoked;
+ int failedInvoked;
+ map<int, int> startSections;
+ map<int, int> finishSections;
+
+ TestListener() : startInvoked(0), finishInvoked(0), failedInvoked(0) {};
+ virtual ~TestListener() {};
+
+ virtual Status onReportStarted() {
+ startInvoked++;
+ return Status::ok();
+ };
+ virtual Status onReportSectionStatus(int section, int status) {
+ switch (status) {
+ case IIncidentReportStatusListener::STATUS_STARTING:
+ if (startSections.count(section) == 0)
+ startSections[section] = 0;
+ startSections[section] = startSections[section] + 1;
+ break;
+ case IIncidentReportStatusListener::STATUS_FINISHED:
+ if (finishSections.count(section) == 0)
+ finishSections[section] = 0;
+ finishSections[section] = finishSections[section] + 1;
+ break;
+ }
+ return Status::ok();
+ };
+ virtual Status onReportFinished() {
+ finishInvoked++;
+ return Status::ok();
+ };
+ virtual Status onReportFailed() {
+ failedInvoked++;
+ return Status::ok();
+ };
+
+protected:
+ IBinder* onAsBinder() override { return nullptr; };
+
+};
+
+class ReporterTest : public Test {
+public:
+ virtual void SetUp() {
+ reporter = new Reporter(td.path);
+ l = new TestListener();
+ }
+
+ vector<string> InspectFiles() {
+ DIR* dir;
+ struct dirent* entry;
+ vector<string> results;
+
+ string dirbase = string(td.path) + "/";
+ dir = opendir(td.path);
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ string filename = dirbase + entry->d_name;
+ string content;
+ ReadFileToString(filename, &content);
+ results.push_back(content);
+ }
+ return results;
+ }
+
+protected:
+ TemporaryDir td;
+ ReportRequestSet requests;
+ sp<Reporter> reporter;
+ sp<TestListener> l;
+};
+
+TEST_F(ReporterTest, IncidentReportArgs) {
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args2.addSection(3);
+
+ args1.merge(args2);
+ ASSERT_TRUE(args1.containsSection(1));
+ ASSERT_FALSE(args1.containsSection(2));
+ ASSERT_TRUE(args1.containsSection(3));
+}
+
+TEST_F(ReporterTest, ReportRequestSetEmpty) {
+ requests.setMainFd(STDOUT_FILENO);
+
+ CaptureStdout();
+ requests.write((uint8_t *) "abcdef", 6);
+ EXPECT_THAT(GetCapturedStdout(), StrEq("abcdef"));
+}
+
+TEST_F(ReporterTest, WriteToStreamFdAndMainFd) {
+ TemporaryFile tf;
+ IncidentReportArgs args;
+ sp<ReportRequest> r = new ReportRequest(args, l, tf.fd);
+
+ requests.add(r);
+ requests.setMainFd(STDOUT_FILENO);
+
+ const char* data = "abcdef";
+
+ CaptureStdout();
+ requests.write((uint8_t *) data, 6);
+ EXPECT_THAT(GetCapturedStdout(), StrEq(data));
+
+ string content;
+ ASSERT_TRUE(ReadFileToString(tf.path, &content));
+ EXPECT_THAT(content, StrEq(data));
+}
+
+TEST_F(ReporterTest, RunReportEmpty) {
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ EXPECT_EQ(l->startInvoked, 0);
+ EXPECT_EQ(l->finishInvoked, 0);
+ EXPECT_TRUE(l->startSections.empty());
+ EXPECT_TRUE(l->finishSections.empty());
+ EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportWithHeaders) {
+ IncidentReportArgs args1, args2;
+ args1.addSection(1);
+ args2.addSection(2);
+ std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
+ args2.addHeader(header);
+ sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
+ sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+
+ reporter->batch.add(r1);
+ reporter->batch.add(r2);
+
+ CaptureStdout();
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+ EXPECT_EQ(l->startInvoked, 2);
+ EXPECT_EQ(l->finishInvoked, 2);
+ EXPECT_TRUE(l->startSections.empty());
+ EXPECT_TRUE(l->finishSections.empty());
+ EXPECT_EQ(l->failedInvoked, 0);
+}
+
+TEST_F(ReporterTest, RunReportToGivenDirectory) {
+ IncidentReportArgs args;
+ args.addHeader({'1', '2', '3'});
+ args.addHeader({'a', 'b', 'c', 'd'});
+ sp<ReportRequest> r = new ReportRequest(args, l, -1);
+ reporter->batch.add(r);
+
+ ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
+ vector<string> results = InspectFiles();
+ ASSERT_EQ((int)results.size(), 1);
+ EXPECT_EQ(results[0], "\n\x3" "123\n\x4" "abcd");
+}
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 18322b6..93771ff 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -22,6 +22,8 @@
#include <gtest/gtest.h>
#include <string.h>
+const int QUICK_TIMEOUT_MS = 100;
+
using namespace android::base;
using namespace std;
using ::testing::StrEq;
@@ -62,7 +64,56 @@
TEST(SectionTest, FileSectionTimeout) {
TemporaryFile tf;
// id -1 is timeout parser
- FileSection fs(-1, tf.path);
+ FileSection fs(-1, tf.path, QUICK_TIMEOUT_MS);
ReportRequestSet requests;
ASSERT_EQ(NO_ERROR, fs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionConstructor) {
+ CommandSection cs1(1, "echo", "\"this is a test\"", "ooo", NULL);
+ CommandSection cs2(2, "single_command", NULL);
+ CommandSection cs3(1, 3123, "echo", "\"this is a test\"", "ooo", NULL);
+ CommandSection cs4(2, 43214, "single_command", NULL);
+
+ EXPECT_THAT(cs1.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs2.name.string(), StrEq("single_command"));
+ EXPECT_EQ(3123, cs3.timeoutMs);
+ EXPECT_EQ(43214, cs4.timeoutMs);
+ EXPECT_THAT(cs3.name.string(), StrEq("echo \"this is a test\" ooo"));
+ EXPECT_THAT(cs4.name.string(), StrEq("single_command"));
+}
+
+TEST(SectionTest, CommandSectionEcho) {
+ CommandSection cs(0, "/system/bin/echo", "about", NULL);
+ ReportRequestSet requests;
+ requests.setMainFd(STDOUT_FILENO);
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+ EXPECT_THAT(GetCapturedStdout(), StrEq("\x02\x06\ntuoba"));
+}
+
+TEST(SectionTest, CommandSectionCommandTimeout) {
+ CommandSection cs(0, QUICK_TIMEOUT_MS, "/system/bin/yes", NULL);
+ ReportRequestSet requests;
+ ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionIncidentHelperTimeout) {
+ CommandSection cs(-1, QUICK_TIMEOUT_MS, "/system/bin/echo", "about", NULL);
+ ReportRequestSet requests;
+ requests.setMainFd(STDOUT_FILENO);
+ ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommand) {
+ CommandSection cs(0, "echo", "about", NULL);
+ ReportRequestSet requests;
+ ASSERT_EQ(NAME_NOT_FOUND, cs.Execute(&requests));
+}
+
+TEST(SectionTest, CommandSectionBadCommandAndTimeout) {
+ CommandSection cs(-1, QUICK_TIMEOUT_MS, "nonexistcommand", "-opt", NULL);
+ ReportRequestSet requests;
+ // timeout will return first
+ ASSERT_EQ(NO_ERROR, cs.Execute(&requests));
}
\ No newline at end of file
diff --git a/core/java/android/annotation/TargetApi.java b/core/java/android/annotation/TargetApi.java
index ea17890..975318e 100644
--- a/core/java/android/annotation/TargetApi.java
+++ b/core/java/android/annotation/TargetApi.java
@@ -16,6 +16,7 @@
package android.annotation;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
@@ -25,7 +26,7 @@
/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
project target is. */
-@Target({TYPE, METHOD, CONSTRUCTOR})
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface TargetApi {
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d432544..a0082e4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2110,7 +2110,7 @@
* is no longer available to third party
* applications: the introduction of document-centric recents means
* it can leak person information to the caller. For backwards compatibility,
- * it will still retu rn a small subset of its data: at least the caller's
+ * it will still return a small subset of its data: at least the caller's
* own tasks, and possibly some other tasks
* such as home that are known to not be sensitive.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c7eb29f..2954234 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5732,6 +5732,7 @@
// probably end up doing the same disk access.
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
+ final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -5759,17 +5760,21 @@
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
+ try {
+ mInstrumentation.callApplicationOnCreate(app);
+ } catch (Exception e) {
+ if (!mInstrumentation.onException(app, e)) {
+ throw new RuntimeException(
+ "Unable to create application " + app.getClass().getName()
+ + ": " + e.toString(), e);
+ }
+ }
} finally {
- StrictMode.setThreadPolicy(savedPolicy);
- }
-
- try {
- mInstrumentation.callApplicationOnCreate(app);
- } catch (Exception e) {
- if (!mInstrumentation.onException(app, e)) {
- throw new RuntimeException(
- "Unable to create application " + app.getClass().getName()
- + ": " + e.toString(), e);
+ // If the app targets < O-MR1, or doesn't change the thread policy
+ // during startup, clobber the policy to maintain behavior of b/36951662
+ if (data.appInfo.targetSdkVersion <= Build.VERSION_CODES.O
+ || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
+ StrictMode.setThreadPolicy(savedPolicy);
}
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 0e33934..49d58eb 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -144,15 +144,15 @@
* or {@link WallpaperManager#FLAG_SYSTEM}
* @return colors of chosen wallpaper
*/
- WallpaperColors getWallpaperColors(int which);
+ WallpaperColors getWallpaperColors(int which, int userId);
/**
* Register a callback to receive color updates
*/
- void registerWallpaperColorsCallback(IWallpaperManagerCallback cb);
+ void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
/**
* Unregister a callback that was receiving color updates
*/
- void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb);
+ void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId);
}
diff --git a/core/java/android/app/IWallpaperManagerCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl
index 0cfbaef..ea0ceab 100644
--- a/core/java/android/app/IWallpaperManagerCallback.aidl
+++ b/core/java/android/app/IWallpaperManagerCallback.aidl
@@ -34,6 +34,6 @@
/**
* Called when wallpaper colors change
*/
- void onWallpaperColorsChanged(in WallpaperColors colors, int which);
+ void onWallpaperColorsChanged(in WallpaperColors colors, int which, int userId);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8858172..34343e9 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -204,7 +204,12 @@
public static final int IMPORTANCE_NONE = 0;
/**
- * Min notification importance: only shows in the shade, below the fold.
+ * Min notification importance: only shows in the shade, below the fold. This should
+ * not be used with {@link Service#startForeground(int, Notification) Service.startForeground}
+ * since a foreground service is supposed to be something the user cares about so it does
+ * not make semantic sense to mark its notification as minimum importance. If you do this
+ * as of Android version {@link android.os.Build.VERSION_CODES#O}, the system will show
+ * a higher-priority notification about your app running in the background.
*/
public static final int IMPORTANCE_MIN = 1;
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index d0791cf..2a8130f 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -408,4 +408,13 @@
return new Size(newWidth, newHeight);
}
+
+ @Override
+ public String toString() {
+ final StringBuilder colors = new StringBuilder();
+ for (int i = 0; i < mMainColors.size(); i++) {
+ colors.append(Integer.toHexString(mMainColors.get(i).toArgb())).append(" ");
+ }
+ return "[WallpaperColors: " + colors.toString() + "h: " + mColorHints + "]";
+ }
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 0398b47..16d0214 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -307,13 +307,14 @@
* changes its colors.
* @param callback Listener
* @param handler Thread to call it from. Main thread if null.
+ * @param userId Owner of the wallpaper or UserHandle.USER_ALL
*/
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
- @Nullable Handler handler) {
+ @Nullable Handler handler, int userId) {
synchronized (this) {
if (!mColorCallbackRegistered) {
try {
- mService.registerWallpaperColorsCallback(this);
+ mService.registerWallpaperColorsCallback(this, userId);
mColorCallbackRegistered = true;
} catch (RemoteException e) {
// Failed, service is gone
@@ -328,15 +329,17 @@
* Stop listening to wallpaper color events.
*
* @param callback listener
+ * @param userId Owner of the wallpaper or UserHandle.USER_ALL
*/
- public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
+ public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
+ int userId) {
synchronized (this) {
mColorListeners.removeIf(pair -> pair.first == callback);
if (mColorListeners.size() == 0 && mColorCallbackRegistered) {
mColorCallbackRegistered = false;
try {
- mService.unregisterWallpaperColorsCallback(this);
+ mService.unregisterWallpaperColorsCallback(this, userId);
} catch (RemoteException e) {
// Failed, service is gone
Log.w(TAG, "Can't unregister color updates", e);
@@ -346,7 +349,7 @@
}
@Override
- public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
synchronized (this) {
for (Pair<OnColorsChangedListener, Handler> listener : mColorListeners) {
Handler handler = listener.second;
@@ -361,21 +364,21 @@
stillExists = mColorListeners.contains(listener);
}
if (stillExists) {
- listener.first.onColorsChanged(colors, which);
+ listener.first.onColorsChanged(colors, which, userId);
}
});
}
}
}
- WallpaperColors getWallpaperColors(int which) {
+ WallpaperColors getWallpaperColors(int which, int userId) {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException(
"Must request colors for exactly one kind of wallpaper");
}
try {
- return mService.getWallpaperColors(which);
+ return mService.getWallpaperColors(which, userId);
} catch (RemoteException e) {
// Can't get colors, connection lost.
}
@@ -857,7 +860,7 @@
* @param listener A listener to register
*/
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener) {
- sGlobals.addOnColorsChangedListener(listener, null);
+ addOnColorsChangedListener(listener, null);
}
/**
@@ -868,25 +871,61 @@
*/
public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
@NonNull Handler handler) {
- sGlobals.addOnColorsChangedListener(listener, handler);
+ addOnColorsChangedListener(listener, handler, mContext.getUserId());
+ }
+
+ /**
+ * Registers a listener to get notified when the wallpaper colors change
+ * @param listener A listener to register
+ * @param handler Where to call it from. Will be called from the main thread
+ * if null.
+ * @param userId Owner of the wallpaper or UserHandle.USER_ALL.
+ * @hide
+ */
+ public void addOnColorsChangedListener(@NonNull OnColorsChangedListener listener,
+ @NonNull Handler handler, int userId) {
+ sGlobals.addOnColorsChangedListener(listener, handler, userId);
}
/**
* Stop listening to color updates.
- * @param callback A callback to unsubscribe
+ * @param callback A callback to unsubscribe.
*/
public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback) {
- sGlobals.removeOnColorsChangedListener(callback);
+ removeOnColorsChangedListener(callback, mContext.getUserId());
+ }
+
+ /**
+ * Stop listening to color updates.
+ * @param callback A callback to unsubscribe.
+ * @param userId Owner of the wallpaper or UserHandle.USER_ALL.
+ * @hide
+ */
+ public void removeOnColorsChangedListener(@NonNull OnColorsChangedListener callback,
+ int userId) {
+ sGlobals.removeOnColorsChangedListener(callback, userId);
}
/**
* Get the primary colors of a wallpaper
* @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
* {@link #FLAG_LOCK}
- * @return a list of colors ordered by priority
+ * @return {@link WallpaperColors} or null if colors are unknown.
*/
public @Nullable WallpaperColors getWallpaperColors(int which) {
- return sGlobals.getWallpaperColors(which);
+ return getWallpaperColors(which, mContext.getUserId());
+ }
+
+ /**
+ * Get the primary colors of a wallpaper
+ * @param which wallpaper type. Must be either {@link #FLAG_SYSTEM} or
+ * {@link #FLAG_LOCK}
+ * @param userId Owner of the wallpaper.
+ * @return {@link WallpaperColors} or null if colors are unknown.
+ * @hide
+ */
+ public @Nullable WallpaperColors getWallpaperColors(int which, int userId) {
+ return sGlobals.getWallpaperColors(which, userId);
}
/**
@@ -1902,9 +1941,9 @@
}
@Override
- public void onWallpaperColorsChanged(WallpaperColors colors, int which)
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId)
throws RemoteException {
- sGlobals.onWallpaperColorsChanged(colors, which);
+ sGlobals.onWallpaperColorsChanged(colors, which, userId);
}
}
@@ -1921,5 +1960,19 @@
* @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
*/
void onColorsChanged(WallpaperColors colors, int which);
+
+ /**
+ * Called when colors change.
+ * A {@link android.app.WallpaperColors} object containing a simplified
+ * color histogram will be given.
+ *
+ * @param colors Wallpaper color info
+ * @param which A combination of {@link #FLAG_LOCK} and {@link #FLAG_SYSTEM}
+ * @param userId Owner of the wallpaper
+ * @hide
+ */
+ default void onColorsChanged(WallpaperColors colors, int which, int userId) {
+ onColorsChanged(colors, which);
+ }
}
}
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index f987468..36f5f96 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -18,20 +18,19 @@
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.BitmapFactory;
-import android.graphics.Point;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.util.Slog;
-import android.view.Display;
-import android.view.WindowManager;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
/**
- * Helper for backing up / restoring wallpapers. Basically an AbsoluteFileBackupHelper,
- * but with logic for deciding what to do with restored wallpaper images.
+ * We no longer back up wallpapers with this helper, but we do need to process restores
+ * of legacy backup payloads. We just take the restored image as-is and apply it as the
+ * system wallpaper using the public "set the wallpaper" API.
*
* @hide
*/
@@ -39,83 +38,34 @@
private static final String TAG = "WallpaperBackupHelper";
private static final boolean DEBUG = false;
- // If 'true', then apply an acceptable-size heuristic at restore time, dropping back
- // to the factory default wallpaper if the restored one differs "too much" from the
- // device's preferred wallpaper image dimensions.
- private static final boolean REJECT_OUTSIZED_RESTORE = false;
-
- // When outsized restore rejection is enabled, this is the maximum ratio between the
- // source and target image heights that will be permitted. The ratio is checked both
- // ways (i.e. >= MAX, or <= 1/MAX) to validate restores from both largeer-than-target
- // and smaller-than-target sources.
- private static final double MAX_HEIGHT_RATIO = 1.35;
-
- // The height ratio check when applying larger images on smaller screens is separate;
- // in current policy we accept any such restore regardless of the relative dimensions.
- private static final double MIN_HEIGHT_RATIO = 0;
-
- // This path must match what the WallpaperManagerService uses
- // TODO: Will need to change if backing up non-primary user's wallpaper
- // http://b/22388012
- public static final String WALLPAPER_IMAGE =
- new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
- "wallpaper").getAbsolutePath();
- public static final String WALLPAPER_ORIG_IMAGE =
- new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
- "wallpaper_orig").getAbsolutePath();
- public static final String WALLPAPER_INFO =
- new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
- "wallpaper_info.xml").getAbsolutePath();
- // Use old keys to keep legacy data compatibility and avoid writing two wallpapers
+ // Key that legacy wallpaper imagery was stored under
public static final String WALLPAPER_IMAGE_KEY =
"/data/data/com.android.settings/files/wallpaper";
public static final String WALLPAPER_INFO_KEY = "/data/system/wallpaper_info.xml";
- // Stage file - should be adjacent to the WALLPAPER_IMAGE location. The wallpapers
- // will be saved to this file from the restore stream, then renamed to the proper
- // location if it's deemed suitable.
- // TODO: Will need to change if backing up non-primary user's wallpaper
- // http://b/22388012
+ // Stage file that the restored imagery is stored to prior to being applied
+ // as the system wallpaper.
private static final String STAGE_FILE =
new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
"wallpaper-tmp").getAbsolutePath();
- Context mContext;
- String[] mFiles;
- String[] mKeys;
- double mDesiredMinWidth;
- double mDesiredMinHeight;
+ private final String[] mKeys;
+ private final WallpaperManager mWpm;
/**
- * Construct a helper for backing up / restoring the files at the given absolute locations
- * within the file system.
+ * Legacy wallpaper restores, from back when the imagery was stored under the
+ * "android" system package as file key/value entities.
*
* @param context
* @param files
*/
- public WallpaperBackupHelper(Context context, String[] files, String[] keys) {
+ public WallpaperBackupHelper(Context context, String[] keys) {
super(context);
mContext = context;
- mFiles = files;
mKeys = keys;
- final WindowManager wm =
- (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- final WallpaperManager wpm =
- (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
- final Display d = wm.getDefaultDisplay();
- final Point size = new Point();
- d.getSize(size);
- mDesiredMinWidth = Math.min(size.x, size.y);
- mDesiredMinHeight = (double) wpm.getDesiredMinimumHeight();
- if (mDesiredMinHeight <= 0) {
- mDesiredMinHeight = size.y;
- }
-
- if (DEBUG) {
- Slog.d(TAG, "dmW=" + mDesiredMinWidth + " dmH=" + mDesiredMinHeight);
- }
+ mWpm = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
}
/**
@@ -126,13 +76,12 @@
@Override
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
- performBackup_checked(oldState, data, newState, mFiles, mKeys);
+ // Intentionally no-op; we don't back up the wallpaper this way any more.
}
/**
* Restore one absolute file entity from the restore stream. If we're restoring the
- * magic wallpaper file, take specific action to determine whether it is suitable for
- * the current device.
+ * magic wallpaper file, apply it as the system wallpaper.
*/
@Override
public void restoreEntity(BackupDataInputStream data) {
@@ -140,69 +89,21 @@
if (isKeyInList(key, mKeys)) {
if (key.equals(WALLPAPER_IMAGE_KEY)) {
// restore the file to the stage for inspection
- File f = new File(STAGE_FILE);
- if (writeFile(f, data)) {
-
- // Preflight the restored image's dimensions without loading it
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(STAGE_FILE, options);
-
- if (DEBUG) Slog.d(TAG, "Restoring wallpaper image w=" + options.outWidth
- + " h=" + options.outHeight);
-
- if (REJECT_OUTSIZED_RESTORE) {
- // We accept any wallpaper that is at least as wide as our preference
- // (i.e. wide enough to fill the screen), and is within a comfortable
- // factor of the target height, to avoid significant clipping/scaling/
- // letterboxing. At this point we know that mDesiredMinWidth is the
- // smallest dimension, regardless of current orientation, so we can
- // safely require that the candidate's width and height both exceed
- // that hard minimum.
- final double heightRatio = mDesiredMinHeight / options.outHeight;
- if (options.outWidth < mDesiredMinWidth
- || options.outHeight < mDesiredMinWidth
- || heightRatio >= MAX_HEIGHT_RATIO
- || heightRatio <= MIN_HEIGHT_RATIO) {
- // Not wide enough for the screen, or too short/tall to be a good fit
- // for the height of the screen, broken image file, or the system's
- // desires for wallpaper size are in a bad state. Probably one of the
- // first two.
- Slog.i(TAG, "Restored image dimensions (w="
- + options.outWidth + ", h=" + options.outHeight
- + ") too far off target (tw="
- + mDesiredMinWidth + ", th=" + mDesiredMinHeight
- + "); falling back to default wallpaper.");
- f.delete();
- return;
+ File stage = new File(STAGE_FILE);
+ try {
+ if (writeFile(stage, data)) {
+ try (FileInputStream in = new FileInputStream(stage)) {
+ mWpm.setStream(in);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to set restored wallpaper: " + e.getMessage());
}
+ } else {
+ Slog.e(TAG, "Unable to save restored wallpaper");
}
-
- // We passed the acceptable-dimensions test (if any), so we're going to
- // use the restored image. That comes last, when we are done restoring
- // both the pixels and the metadata.
+ } finally {
+ stage.delete();
}
- } else if (key.equals(WALLPAPER_INFO_KEY)) {
- // XML file containing wallpaper info
- File f = new File(WALLPAPER_INFO);
- writeFile(f, data);
}
}
}
-
- /**
- * Hook for the agent to call this helper upon completion of the restore. We do this
- * upon completion so that we know both the imagery and the wallpaper info have
- * been emplaced without requiring either or relying on ordering.
- */
- public void onRestoreFinished() {
- final File f = new File(STAGE_FILE);
- if (f.exists()) {
- // TODO: spin a service to copy the restored image to sd/usb storage,
- // since it does not exist anywhere other than the private wallpaper
- // file.
- Slog.d(TAG, "Applying restored wallpaper image.");
- f.renameTo(new File(WALLPAPER_ORIG_IMAGE));
- }
- }
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index ea675fb..c3ebf55 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1406,8 +1406,9 @@
// Icon may have been omitted for calls that return bulk session
// lists, so try fetching the specific icon.
try {
- appIcon = AppGlobals.getPackageManager().getPackageInstaller()
- .getSessionInfo(sessionId).appIcon;
+ final SessionInfo info = AppGlobals.getPackageManager().getPackageInstaller()
+ .getSessionInfo(sessionId);
+ appIcon = (info != null) ? info.appIcon : null;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 63eedf5..55343a2 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -73,8 +73,10 @@
* Create a request suitable for still image capture. Specifically, this
* means prioritizing image quality over frame rate. These requests would
* commonly be used with the {@link CameraCaptureSession#capture} method.
- * This template is guaranteed to be supported on all camera devices.
- *
+ * This template is guaranteed to be supported on all camera devices except
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+ * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+ * BACKWARD_COMPATIBLE}.
* @see #createCaptureRequest
*/
public static final int TEMPLATE_STILL_CAPTURE = 2;
@@ -84,7 +86,10 @@
* that a stable frame rate is used, and post-processing is set for
* recording quality. These requests would commonly be used with the
* {@link CameraCaptureSession#setRepeatingRequest} method.
- * This template is guaranteed to be supported on all camera devices.
+ * This template is guaranteed to be supported on all camera devices except
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+ * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+ * BACKWARD_COMPATIBLE}.
*
* @see #createCaptureRequest
*/
@@ -98,7 +103,10 @@
* {@link #TEMPLATE_RECORD} is is in use with {@link CameraCaptureSession#setRepeatingRequest}.
* This template is guaranteed to be supported on all camera devices except
* legacy devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
- * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY})
+ * {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY}) and
+ * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT} devices
+ * that are not {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
+ * BACKWARD_COMPATIBLE}.
*
* @see #createCaptureRequest
*/
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index c3bbaec..a09a686 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -23,7 +23,7 @@
oneway interface ITunerCallback {
void onError(int status);
void onConfigurationChanged(in RadioManager.BandConfig config);
- void onProgramInfoChanged();
+ void onCurrentProgramInfoChanged();
void onTrafficAnnouncement(boolean active);
void onEmergencyAnnouncement(boolean active);
void onAntennaState(boolean connected);
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index 00a36c8..02ca52e 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -49,7 +49,7 @@
synchronized (mLock) {
if (mTuner != null) throw new IllegalStateException();
mTuner = tuner;
- if (mPendingProgramInfoChanged) onProgramInfoChanged();
+ if (mPendingProgramInfoChanged) onCurrentProgramInfoChanged();
}
}
@@ -64,7 +64,7 @@
}
@Override
- public void onProgramInfoChanged() {
+ public void onCurrentProgramInfoChanged() {
synchronized (mLock) {
if (mTuner == null) {
mPendingProgramInfoChanged = true;
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index ed223d1..d2e3510 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -16,10 +16,6 @@
package android.inputmethodservice;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputMethodSession;
-
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -34,9 +30,13 @@
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.InputMethodSession;
-import android.view.inputmethod.CursorAnchorInfo;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputMethodSession;
class IInputMethodSessionWrapper extends IInputMethodSession.Stub
implements HandlerCaller.Callback {
@@ -218,7 +218,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
if (mInputMethodSession == null) {
// The session has been finished.
finishInputEvent(event, false);
diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java
index fdb4ba0..a70c97b 100644
--- a/core/java/android/net/NetworkRecommendationProvider.java
+++ b/core/java/android/net/NetworkRecommendationProvider.java
@@ -35,6 +35,7 @@
* A network recommendation provider is any application which:
* <ul>
* <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
* <li>Includes a Service for the {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS} intent
* which is protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE} permission.
* </ul>
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 7e0c9ce..060af0d 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -38,7 +38,8 @@
*
* <p>A network scorer is any application which:
* <ul>
- * <li>Declares the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#SCORE_NETWORKS} permission.
+ * <li>Is granted the {@link permission#ACCESS_COARSE_LOCATION} permission.
* <li>Include a Service for the {@link #ACTION_RECOMMEND_NETWORKS} action
* protected by the {@link permission#BIND_NETWORK_RECOMMENDATION_SERVICE}
* permission.
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index ace3748..1e41eea 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -16,6 +16,10 @@
package android.net.nsd;
+import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.Preconditions.checkStringNotEmpty;
+
import android.annotation.SdkConstant;
import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
@@ -240,12 +244,12 @@
return name;
}
+ private static int FIRST_LISTENER_KEY = 1;
+
private final INsdManager mService;
private final Context mContext;
- private static final int INVALID_LISTENER_KEY = 0;
- private static final int BUSY_LISTENER_KEY = -1;
- private int mListenerKey = 1;
+ private int mListenerKey = FIRST_LISTENER_KEY;
private final SparseArray mListenerMap = new SparseArray();
private final SparseArray<NsdServiceInfo> mServiceMap = new SparseArray<>();
private final Object mMapLock = new Object();
@@ -311,7 +315,6 @@
public void onServiceFound(NsdServiceInfo serviceInfo);
public void onServiceLost(NsdServiceInfo serviceInfo);
-
}
/** Interface for callback invocation for service registration */
@@ -342,8 +345,9 @@
@Override
public void handleMessage(Message message) {
- if (DBG) Log.d(TAG, "received " + nameOf(message.what));
- switch (message.what) {
+ final int what = message.what;
+ final int key = message.arg2;
+ switch (what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
return;
@@ -356,19 +360,26 @@
default:
break;
}
- Object listener = getListener(message.arg2);
+ final Object listener;
+ final NsdServiceInfo ns;
+ synchronized (mMapLock) {
+ listener = mListenerMap.get(key);
+ ns = mServiceMap.get(key);
+ }
if (listener == null) {
Log.d(TAG, "Stale key " + message.arg2);
return;
}
- NsdServiceInfo ns = getNsdService(message.arg2);
- switch (message.what) {
+ if (DBG) {
+ Log.d(TAG, "received " + nameOf(what) + " for key " + key + ", service " + ns);
+ }
+ switch (what) {
case DISCOVER_SERVICES_STARTED:
String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
((DiscoveryListener) listener).onDiscoveryStarted(s);
break;
case DISCOVER_SERVICES_FAILED:
- removeListener(message.arg2);
+ removeListener(key);
((DiscoveryListener) listener).onStartDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
break;
@@ -381,16 +392,16 @@
case STOP_DISCOVERY_FAILED:
// TODO: failure to stop discovery should be internal and retried internally, as
// the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
- removeListener(message.arg2);
+ removeListener(key);
((DiscoveryListener) listener).onStopDiscoveryFailed(getNsdServiceInfoType(ns),
message.arg1);
break;
case STOP_DISCOVERY_SUCCEEDED:
- removeListener(message.arg2);
+ removeListener(key);
((DiscoveryListener) listener).onDiscoveryStopped(getNsdServiceInfoType(ns));
break;
case REGISTER_SERVICE_FAILED:
- removeListener(message.arg2);
+ removeListener(key);
((RegistrationListener) listener).onRegistrationFailed(ns, message.arg1);
break;
case REGISTER_SERVICE_SUCCEEDED:
@@ -398,7 +409,7 @@
(NsdServiceInfo) message.obj);
break;
case UNREGISTER_SERVICE_FAILED:
- removeListener(message.arg2);
+ removeListener(key);
((RegistrationListener) listener).onUnregistrationFailed(ns, message.arg1);
break;
case UNREGISTER_SERVICE_SUCCEEDED:
@@ -408,11 +419,11 @@
((RegistrationListener) listener).onServiceUnregistered(ns);
break;
case RESOLVE_SERVICE_FAILED:
- removeListener(message.arg2);
+ removeListener(key);
((ResolveListener) listener).onResolveFailed(ns, message.arg1);
break;
case RESOLVE_SERVICE_SUCCEEDED:
- removeListener(message.arg2);
+ removeListener(key);
((ResolveListener) listener).onServiceResolved((NsdServiceInfo) message.obj);
break;
default:
@@ -422,40 +433,27 @@
}
}
- // if the listener is already in the map, reject it. Otherwise, add it and
- // return its key.
+ private int nextListenerKey() {
+ // Ensure mListenerKey >= FIRST_LISTENER_KEY;
+ mListenerKey = Math.max(FIRST_LISTENER_KEY, mListenerKey + 1);
+ return mListenerKey;
+ }
+
+ // Assert that the listener is not in the map, then add it and returns its key
private int putListener(Object listener, NsdServiceInfo s) {
- if (listener == null) return INVALID_LISTENER_KEY;
- int key;
+ checkListener(listener);
+ final int key;
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- if (valueIndex != -1) {
- return BUSY_LISTENER_KEY;
- }
- do {
- key = mListenerKey++;
- } while (key == INVALID_LISTENER_KEY);
+ checkArgument(valueIndex == -1, "listener already in use");
+ key = nextListenerKey();
mListenerMap.put(key, listener);
mServiceMap.put(key, s);
}
return key;
}
- private Object getListener(int key) {
- if (key == INVALID_LISTENER_KEY) return null;
- synchronized (mMapLock) {
- return mListenerMap.get(key);
- }
- }
-
- private NsdServiceInfo getNsdService(int key) {
- synchronized (mMapLock) {
- return mServiceMap.get(key);
- }
- }
-
private void removeListener(int key) {
- if (key == INVALID_LISTENER_KEY) return;
synchronized (mMapLock) {
mListenerMap.remove(key);
mServiceMap.remove(key);
@@ -463,16 +461,15 @@
}
private int getListenerKey(Object listener) {
+ checkListener(listener);
synchronized (mMapLock) {
int valueIndex = mListenerMap.indexOfValue(listener);
- if (valueIndex != -1) {
- return mListenerMap.keyAt(valueIndex);
- }
+ checkArgument(valueIndex != -1, "listener not registered");
+ return mListenerMap.keyAt(valueIndex);
}
- return INVALID_LISTENER_KEY;
}
- private String getNsdServiceInfoType(NsdServiceInfo s) {
+ private static String getNsdServiceInfoType(NsdServiceInfo s) {
if (s == null) return "?";
return s.getServiceType();
}
@@ -482,7 +479,9 @@
*/
private void init() {
final Messenger messenger = getMessenger();
- if (messenger == null) throw new RuntimeException("Failed to initialize");
+ if (messenger == null) {
+ fatal("Failed to obtain service Messenger");
+ }
HandlerThread t = new HandlerThread("NsdManager");
t.start();
mHandler = new ServiceHandler(t.getLooper());
@@ -490,10 +489,15 @@
try {
mConnected.await();
} catch (InterruptedException e) {
- Log.e(TAG, "interrupted wait at init");
+ fatal("Interrupted wait at init");
}
}
+ private static void fatal(String msg) {
+ Log.e(TAG, msg);
+ throw new RuntimeException(msg);
+ }
+
/**
* Register a service to be discovered by other services.
*
@@ -513,23 +517,10 @@
*/
public void registerService(NsdServiceInfo serviceInfo, int protocolType,
RegistrationListener listener) {
- if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
- TextUtils.isEmpty(serviceInfo.getServiceType())) {
- throw new IllegalArgumentException("Service name or type cannot be empty");
- }
- if (serviceInfo.getPort() <= 0) {
- throw new IllegalArgumentException("Invalid port number");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
- if (protocolType != PROTOCOL_DNS_SD) {
- throw new IllegalArgumentException("Unsupported protocol");
- }
+ checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+ checkServiceInfo(serviceInfo);
+ checkProtocol(protocolType);
int key = putListener(listener, serviceInfo);
- if (key == BUSY_LISTENER_KEY) {
- throw new IllegalArgumentException("listener already in use");
- }
mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
}
@@ -548,12 +539,6 @@
*/
public void unregisterService(RegistrationListener listener) {
int id = getListenerKey(listener);
- if (id == INVALID_LISTENER_KEY) {
- throw new IllegalArgumentException("listener not registered");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
mAsyncChannel.sendMessage(UNREGISTER_SERVICE, 0, id);
}
@@ -586,25 +571,13 @@
* Cannot be null. Cannot be in use for an active service discovery.
*/
public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
- if (TextUtils.isEmpty(serviceType)) {
- throw new IllegalArgumentException("Service type cannot be empty");
- }
-
- if (protocolType != PROTOCOL_DNS_SD) {
- throw new IllegalArgumentException("Unsupported protocol");
- }
+ checkStringNotEmpty(serviceType, "Service type cannot be empty");
+ checkProtocol(protocolType);
NsdServiceInfo s = new NsdServiceInfo();
s.setServiceType(serviceType);
int key = putListener(listener, s);
- if (key == BUSY_LISTENER_KEY) {
- throw new IllegalArgumentException("listener already in use");
- }
-
mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, key, s);
}
@@ -626,12 +599,6 @@
*/
public void stopServiceDiscovery(DiscoveryListener listener) {
int id = getListenerKey(listener);
- if (id == INVALID_LISTENER_KEY) {
- throw new IllegalArgumentException("service discovery not active on listener");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, id);
}
@@ -645,19 +612,8 @@
* Cannot be in use for an active service resolution.
*/
public void resolveService(NsdServiceInfo serviceInfo, ResolveListener listener) {
- if (TextUtils.isEmpty(serviceInfo.getServiceName()) ||
- TextUtils.isEmpty(serviceInfo.getServiceType())) {
- throw new IllegalArgumentException("Service name or type cannot be empty");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
-
+ checkServiceInfo(serviceInfo);
int key = putListener(listener, serviceInfo);
-
- if (key == BUSY_LISTENER_KEY) {
- throw new IllegalArgumentException("listener already in use");
- }
mAsyncChannel.sendMessage(RESOLVE_SERVICE, 0, key, serviceInfo);
}
@@ -671,10 +627,10 @@
}
/**
- * Get a reference to NetworkService handler. This is used to establish
+ * Get a reference to NsdService handler. This is used to establish
* an AsyncChannel communication with the service
*
- * @return Messenger pointing to the NetworkService handler
+ * @return Messenger pointing to the NsdService handler
*/
private Messenger getMessenger() {
try {
@@ -683,4 +639,18 @@
throw e.rethrowFromSystemServer();
}
}
+
+ private static void checkListener(Object listener) {
+ checkNotNull(listener, "listener cannot be null");
+ }
+
+ private static void checkProtocol(int protocolType) {
+ checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+ }
+
+ private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
+ checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
+ checkStringNotEmpty(serviceInfo.getServiceName(),"Service name cannot be empty");
+ checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+ }
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 7d664ec..efc911a 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3544,6 +3544,7 @@
if (name.indexOf(',') >= 0) {
name = name.replace(',', '_');
}
+ name = name.replaceAll("[\\n|\\r]+", "");
dumpLine(pw, uid, category, WAKELOCK_DATA, name, sb.toString());
}
}
diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java
index 712bbaa..459aeb0 100644
--- a/core/java/android/os/SharedMemory.java
+++ b/core/java/android/os/SharedMemory.java
@@ -28,6 +28,7 @@
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.DirectByteBuffer;
+import java.nio.NioUtils;
import sun.misc.Cleaner;
@@ -191,11 +192,16 @@
}
/**
- * Creates an mmap of the SharedMemory with the specified prot, offset, and length.
+ * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
+ * always produce a new ByteBuffer window to the backing shared memory region. Every call
+ * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
+ * returned by map() is no longer needed.
*
* @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
- * @param offset The offset into the shared memory to begin mapping
- * @param length The length of the region to map
+ * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
+ * getSize().
+ * @param length The length of the region to map. Must be > 0 and offset + length must not
+ * exceed getSize().
* @return A ByteBuffer mapping.
* @throws ErrnoException if the mmap call failed.
*/
@@ -203,7 +209,7 @@
checkOpen();
validateProt(prot);
if (offset < 0) {
- throw new IllegalArgumentException("Offset must be > 0");
+ throw new IllegalArgumentException("Offset must be >= 0");
}
if (length <= 0) {
throw new IllegalArgumentException("Length must be > 0");
@@ -218,15 +224,16 @@
}
/**
- * Unmaps a buffer previously returned by {@link #map(int, int, int)}
+ * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
+ * release the backing memory of the ByteBuffer, invalidating all references to it. Only
+ * call this method if there are no duplicates of the ByteBuffer in use and don't
+ * access the ByteBuffer after calling this method.
+ *
* @param buffer The buffer to unmap
*/
public static void unmap(@NonNull ByteBuffer buffer) {
if (buffer instanceof DirectByteBuffer) {
- Cleaner cleaner = ((DirectByteBuffer) buffer).cleaner();
- if (cleaner != null) {
- cleaner.clean();
- }
+ NioUtils.freeDirectBuffer(buffer);
} else {
throw new IllegalArgumentException(
"ByteBuffer wasn't created by #map(int, int, int); can't unmap");
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 2298134..81b1921 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -66,8 +66,6 @@
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.LinkedList;
import java.util.Objects;
@@ -635,30 +633,6 @@
}
/**
- * Returns metadata for arbitrary file given its stream and mimetype.
- *
- * <p><b>Note: Providers should only call this with streams for locally cached resources.
- * Use of network backed streams is inadvisable for performance reasons.
- *
- * @param stream The input stream. Should be backed by locally cached content.
- * Client retains ownership of the stream.
- * @param mimeType The mime type of the file.
- * @param tags The list of tags to load, if known. Pass null to get a default set.
- * @return Bundle containing any metadata found.
- * @throws IOException in the event of an error reading metadata.
- *
- * @hide
- */
- protected Bundle getDocumentMetadataFromStream(InputStream stream, String mimeType)
- throws IOException {
- Bundle metadata = new Bundle();
- // TODO: Remove the last null arg from MetadataReader. It was the "tags" value,
- // the has been removed from the getDocumentMetadata method.
- MetadataReader.getMetadata(metadata, stream, mimeType, null);
- return metadata;
- }
-
- /**
* Return concrete MIME type of the requested document. Must match the value
* of {@link Document#COLUMN_MIME_TYPE} for this document. The default
* implementation queries {@link #queryDocument(String, String[])}, so
diff --git a/core/java/android/provider/MetadataReader.java b/core/java/android/provider/MetadataReader.java
index 5aa5f72..4f3a7d6 100644
--- a/core/java/android/provider/MetadataReader.java
+++ b/core/java/android/provider/MetadataReader.java
@@ -36,29 +36,33 @@
*/
public final class MetadataReader {
- private MetadataReader() {
- }
+ private MetadataReader() {}
private static final String[] DEFAULT_EXIF_TAGS = {
- ExifInterface.TAG_IMAGE_WIDTH,
- ExifInterface.TAG_IMAGE_LENGTH,
+ ExifInterface.TAG_APERTURE,
+ ExifInterface.TAG_COPYRIGHT,
ExifInterface.TAG_DATETIME,
+ ExifInterface.TAG_EXPOSURE_TIME,
+ ExifInterface.TAG_FOCAL_LENGTH,
+ ExifInterface.TAG_F_NUMBER,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LATITUDE_REF,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_LONGITUDE_REF,
+ ExifInterface.TAG_IMAGE_LENGTH,
+ ExifInterface.TAG_IMAGE_WIDTH,
+ ExifInterface.TAG_ISO_SPEED_RATINGS,
ExifInterface.TAG_MAKE,
ExifInterface.TAG_MODEL,
- ExifInterface.TAG_APERTURE,
- ExifInterface.TAG_SHUTTER_SPEED_VALUE
+ ExifInterface.TAG_ORIENTATION,
+ ExifInterface.TAG_SHUTTER_SPEED_VALUE,
};
- private static final Map<String, Integer> TYPE_MAPPING = new HashMap<>();
- private static final String[] ALL_KNOWN_EXIF_KEYS;
private static final int TYPE_INT = 0;
private static final int TYPE_DOUBLE = 1;
private static final int TYPE_STRING = 2;
+ private static final Map<String, Integer> TYPE_MAPPING = new HashMap<>();
static {
// TODO: Move this over to ExifInterface.java
// Since each ExifInterface item has a type, and there's currently no way to get the type
@@ -198,11 +202,19 @@
TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, TYPE_INT);
TYPE_MAPPING.put(ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, TYPE_INT);
TYPE_MAPPING.put(ExifInterface.TAG_RW2_ISO, TYPE_INT);
- ALL_KNOWN_EXIF_KEYS = TYPE_MAPPING.keySet().toArray(new String[TYPE_MAPPING.size()]);
}
private static final String JPG_MIME_TYPE = "image/jpg";
private static final String JPEG_MIME_TYPE = "image/jpeg";
+
+ /**
+ * Returns true if caller can generally expect to get metadata results
+ * for the supplied mimetype.
+ */
+ public static boolean isSupportedMimeType(String mimeType) {
+ return JPG_MIME_TYPE.equals(mimeType) || JPEG_MIME_TYPE.equals(mimeType);
+ }
+
/**
* Generic metadata retrieval method that can retrieve any available metadata from a given doc
* Currently only functions for exifdata
@@ -211,27 +223,14 @@
* @param stream InputStream containing a file
* @param mimeType type of the given file
* @param tags a variable amount of keys to differentiate which tags the user wants
- * if null, returns a default set of data from the following keys:
- * Exif data:
- * ExifInterface.TAG_IMAGE_WIDTH,
- * ExifInterface.TAG_IMAGE_LENGTH,
- * ExifInterface.TAG_DATETIME,
- * ExifInterface.TAG_GPS_LATITUDE,
- * ExifInterface.TAG_GPS_LATITUDE_REF,
- * ExifInterface.TAG_GPS_LONGITUDE,
- * ExifInterface.TAG_GPS_LONGITUDE_REF,
- * ExifInterface.TAG_MAKE,
- * ExifInterface.TAG_MODEL,
- * ExifInterface.TAG_APERTURE,
- * ExifInterface.TAG_SHUTTER_SPEED_VALUE
+ * if null, returns a default set of data. See {@link DEFAULT_EXIF_TAGS}.
* @throws IOException when the file doesn't exist
*/
public static void getMetadata(Bundle metadata, InputStream stream, String mimeType,
@Nullable String[] tags) throws IOException {
- List<String> metadataTypes = new ArrayList();
- if (mimeType.equals(JPG_MIME_TYPE) || mimeType.equals(JPEG_MIME_TYPE)) {
- ExifInterface exifInterface = new ExifInterface(stream);
- Bundle exifData = getExifData(exifInterface, tags);
+ List<String> metadataTypes = new ArrayList<>();
+ if (isSupportedMimeType(mimeType)) {
+ Bundle exifData = getExifData(stream, tags);
if (exifData.size() > 0) {
metadata.putBundle(DocumentsContract.METADATA_EXIF, exifData);
metadataTypes.add(DocumentsContract.METADATA_EXIF);
@@ -246,27 +245,17 @@
/**
* Helper method that is called if getMetadata is called for an image mimeType.
*
- * @param exif the bundle to which we add exif data.
- * @param exifInterface an ExifInterface for an image
+ * @param stream the input stream from which to extra data.
* @param tags a list of ExifInterface tags that are used to retrieve data.
- * if null, returns a default set of data from the following keys:
- * ExifInterface.TAG_IMAGE_WIDTH,
- * ExifInterface.TAG_IMAGE_LENGTH,
- * ExifInterface.TAG_DATETIME,
- * ExifInterface.TAG_GPS_LATITUDE,
- * ExifInterface.TAG_GPS_LATITUDE_REF,
- * ExifInterface.TAG_GPS_LONGITUDE,
- * ExifInterface.TAG_GPS_LONGITUDE_REF,
- * ExifInterface.TAG_MAKE,
- * ExifInterface.TAG_MODEL,
- * ExifInterface.TAG_APERTURE,
- * ExifInterface.TAG_SHUTTER_SPEED_VALUE
+ * if null, returns a default set of data. See {@link DEFAULT_EXIF_TAGS}.
*/
- private static Bundle getExifData(ExifInterface exifInterface, @Nullable String[] tags)
+ private static Bundle getExifData(InputStream stream, @Nullable String[] tags)
throws IOException {
if (tags == null) {
tags = DEFAULT_EXIF_TAGS;
}
+
+ ExifInterface exifInterface = new ExifInterface(stream);
Bundle exif = new Bundle();
for (String tag : tags) {
if (TYPE_MAPPING.get(tag).equals(TYPE_INT)) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8869a6b..88aafdc 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -576,21 +576,6 @@
"android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
/**
- * Activity Action: Show a dialog to select input method.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you
- * safeguard against this.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: Nothing.
- * @hide
- */
- @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_SHOW_INPUT_METHOD_PICKER =
- "android.settings.SHOW_INPUT_METHOD_PICKER";
-
- /**
* Activity Action: Show settings to manage the user input dictionary.
* <p>
* Starting with {@link android.os.Build.VERSION_CODES#KITKAT},
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index eff52e6..fb6f637 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -30,5 +30,6 @@
void dispatchPointer(in MotionEvent event);
void dispatchWallpaperCommand(String action, int x, int y,
int z, in Bundle extras);
+ void requestWallpaperColors();
void destroy();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 424967f..65d66dc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -105,6 +105,7 @@
private static final int MSG_WINDOW_RESIZED = 10030;
private static final int MSG_WINDOW_MOVED = 10035;
private static final int MSG_TOUCH_EVENT = 10040;
+ private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private final ArrayList<Engine> mActiveEngines
= new ArrayList<Engine>();
@@ -268,7 +269,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
boolean handled = false;
try {
if (event instanceof MotionEvent
@@ -626,8 +627,7 @@
}
Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
mCaller.sendMessage(msg);
- } else {
- event.recycle();
+ } else {event.recycle();
}
}
@@ -1192,6 +1192,11 @@
}
}
+ public void requestWallpaperColors() {
+ Message msg = mCaller.obtainMessage(MSG_REQUEST_WALLPAPER_COLORS);
+ mCaller.sendMessage(msg);
+ }
+
public void destroy() {
Message msg = mCaller.obtainMessage(DO_DETACH);
mCaller.sendMessage(msg);
@@ -1210,7 +1215,6 @@
mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
- engine.notifyColorsChanged();
return;
}
case DO_DETACH: {
@@ -1268,6 +1272,16 @@
}
ev.recycle();
} break;
+ case MSG_REQUEST_WALLPAPER_COLORS: {
+ if (mConnection == null) {
+ break;
+ }
+ try {
+ mConnection.onWallpaperColorsChanged(mEngine.onComputeColors());
+ } catch (RemoteException e) {
+ // Connection went away, nothing to do in here.
+ }
+ } break;
default :
Log.w(TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index 1f1863c..2900c83 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -106,6 +106,7 @@
final PlaybackQueueItem item = it.next();
if (item.getCallerIdentity() == callerIdentity) {
it.remove();
+ stop(item);
}
}
}
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index f52638b..704a1da 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -21,6 +21,7 @@
import android.util.Log;
import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -70,6 +71,11 @@
// wait for the next one.
private ConcurrentLinkedQueue<ProgressMarker> markerList = new ConcurrentLinkedQueue<>();
+ private static final int NOT_RUN = 0;
+ private static final int RUN_CALLED = 1;
+ private static final int STOP_CALLED = 2;
+ private final AtomicInteger mRunState = new AtomicInteger(NOT_RUN);
+
SynthesisPlaybackQueueItem(AudioOutputParams audioParams, int sampleRate,
int audioFormat, int channelCount, UtteranceProgressDispatcher dispatcher,
Object callerIdentity, AbstractEventLogger logger) {
@@ -88,6 +94,11 @@
@Override
public void run() {
+ if (!mRunState.compareAndSet(NOT_RUN, RUN_CALLED)) {
+ // stop() was already called before run(). Do nothing and just finish.
+ return;
+ }
+
final UtteranceProgressDispatcher dispatcher = getDispatcher();
dispatcher.dispatchOnStart();
@@ -120,6 +131,12 @@
mAudioTrack.waitAndRelease();
+ dispatchEndStatus();
+ }
+
+ private void dispatchEndStatus() {
+ final UtteranceProgressDispatcher dispatcher = getDispatcher();
+
if (mStatusCode == TextToSpeech.SUCCESS) {
dispatcher.dispatchOnSuccess();
} else if(mStatusCode == TextToSpeech.STOPPED) {
@@ -140,6 +157,13 @@
mStopped = true;
mStatusCode = statusCode;
+ if (mRunState.getAndSet(STOP_CALLED) == NOT_RUN) {
+ // Dispatch the status code and just finish without signaling
+ // if run() has not even started.
+ dispatchEndStatus();
+ return;
+ }
+
// Wake up the audio playback thread if it was waiting on take().
// take() will return null since mStopped was true, and will then
// break out of the data write loop.
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
index d773158..a507f2b 100644
--- a/core/java/android/text/InputFilter.java
+++ b/core/java/android/text/InputFilter.java
@@ -16,7 +16,9 @@
package android.text;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
import java.util.Locale;
@@ -64,7 +66,8 @@
* Constructs a locale-specific AllCaps filter, to make sure capitalization rules of that
* locale are used for transforming the sequence.
*/
- public AllCaps(@Nullable Locale locale) {
+ public AllCaps(@NonNull Locale locale) {
+ Preconditions.checkNotNull(locale);
mLocale = locale;
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 4140464..3474c2b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -559,7 +559,7 @@
Builder.recycle(b);
}
- /* package */ StaticLayout(@Nullable CharSequence text) {
+ /* package */ StaticLayout(CharSequence text) {
super(text, null, 0, null, 0, 0);
mColumns = COLUMNS_ELLIPSIZE;
@@ -601,7 +601,7 @@
}
/* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
- final CharSequence source = b.mText;
+ CharSequence source = b.mText;
int bufStart = b.mStart;
int bufEnd = b.mEnd;
TextPaint paint = b.mPaint;
@@ -720,7 +720,7 @@
// TODO: Support more justification mode, e.g. letter spacing, stretching.
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE);
if (mLeftIndents != null || mRightIndents != null) {
- // TODO(performance): it would be better to do this once per layout rather
+ // TODO(raph) performance: it would be better to do this once per layout rather
// than once per paragraph, but that would require a change to the native
// interface.
int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -807,7 +807,7 @@
width += widths[j];
}
}
- flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit
+ flag |= flags[i] & TAB_MASK;
}
// Treat the last line and overflowed lines as a single line.
breaks[remainingLineCount - 1] = breaks[breakCount - 1];
@@ -909,16 +909,17 @@
}
}
- // The parameters that are not changed in the method are marked as final to make the code
- // easier to understand.
- private int out(final CharSequence text, final int start, final int end, int above, int below,
- int top, int bottom, int v, final float spacingmult, final float spacingadd,
- final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
- final int flags, final boolean needMultiply, final byte[] chdirs, final int dir,
- final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad,
- final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
- final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
- final float textWidth, final TextPaint paint, final boolean moreChars) {
+ private int out(CharSequence text, int start, int end,
+ int above, int below, int top, int bottom, int v,
+ float spacingmult, float spacingadd,
+ LineHeightSpan[] chooseHt, int[] chooseHtv,
+ Paint.FontMetricsInt fm, int flags,
+ boolean needMultiply, byte[] chdirs, int dir,
+ boolean easy, int bufEnd, boolean includePad,
+ boolean trackPad, boolean addLastLineLineSpacing, char[] chs,
+ float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
+ float ellipsisWidth, float textWidth,
+ TextPaint paint, boolean moreChars) {
final int j = mLineCount;
final int off = j * mColumns;
final int want = off + mColumns + TOP;
@@ -938,30 +939,30 @@
mLineDirections = grow;
}
- lines[off + START] = start;
- lines[off + TOP] = v;
+ if (chooseHt != null) {
+ fm.ascent = above;
+ fm.descent = below;
+ fm.top = top;
+ fm.bottom = bottom;
- // Information about hyphenation, tabs, and directions are needed for determining
- // ellipsization, so the values should be assigned before ellipsization.
+ for (int i = 0; i < chooseHt.length; i++) {
+ if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
+ ((LineHeightSpan.WithDensity) chooseHt[i]).
+ chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
- // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
- // one bit for start field
- lines[off + TAB] |= flags & TAB_MASK;
- lines[off + HYPHEN] = flags;
+ } else {
+ chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
+ }
+ }
- lines[off + DIR] |= dir << DIR_SHIFT;
- // easy means all chars < the first RTL, so no emoji, no nothing
- // XXX a run with no text or all spaces is easy but might be an empty
- // RTL paragraph. Make sure easy is false if this is the case.
- if (easy) {
- mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT;
- } else {
- mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
- start - widthStart, end - start);
+ above = fm.ascent;
+ below = fm.descent;
+ top = fm.top;
+ bottom = fm.bottom;
}
- final boolean firstLine = (j == 0);
- final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
+ boolean firstLine = (j == 0);
+ boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
if (ellipsize != null) {
// If there is only one line, then do any type of ellipsis except when it is MARQUEE
@@ -974,9 +975,9 @@
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
- calculateEllipsis(text, start, end, widths, widthStart,
- ellipsisWidth - getTotalInsets(j), ellipsize, j,
- textWidth, paint, forceEllipsis, dir);
+ calculateEllipsis(start, end, widths, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
}
}
@@ -995,28 +996,6 @@
}
}
- if (chooseHt != null) {
- fm.ascent = above;
- fm.descent = below;
- fm.top = top;
- fm.bottom = bottom;
-
- for (int i = 0; i < chooseHt.length; i++) {
- if (chooseHt[i] instanceof LineHeightSpan.WithDensity) {
- ((LineHeightSpan.WithDensity) chooseHt[i])
- .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint);
-
- } else {
- chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm);
- }
- }
-
- above = fm.ascent;
- below = fm.descent;
- top = fm.top;
- bottom = fm.bottom;
- }
-
if (firstLine) {
if (trackPad) {
mTopPadding = top - above;
@@ -1027,6 +1006,8 @@
}
}
+ int extra;
+
if (lastLine) {
if (trackPad) {
mBottomPadding = bottom - below;
@@ -1037,9 +1018,8 @@
}
}
- final int extra;
if (needMultiply && (addLastLineLineSpacing || !lastLine)) {
- final double ex = (below - above) * (spacingmult - 1) + spacingadd;
+ double ex = (below - above) * (spacingmult - 1) + spacingadd;
if (ex >= 0) {
extra = (int)(ex + EXTRA_ROUNDING);
} else {
@@ -1049,6 +1029,8 @@
extra = 0;
}
+ lines[off + START] = start;
+ lines[off + TOP] = v;
lines[off + DESCENT] = below + extra;
lines[off + EXTRA] = extra;
@@ -1056,7 +1038,7 @@
// store the height as if it was ellipsized
if (!mEllipsized && currentLineIsTheLastVisibleOne) {
// below calculation as if it was the last line
- final int maxLineBelow = includePad ? bottom : below;
+ int maxLineBelow = includePad ? bottom : below;
// similar to the calculation of v below, without the extra.
mMaxLineHeight = v + (maxLineBelow - above);
}
@@ -1065,13 +1047,33 @@
lines[off + mColumns + START] = end;
lines[off + mColumns + TOP] = v;
+ // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
+ // one bit for start field
+ lines[off + TAB] |= flags & TAB_MASK;
+ lines[off + HYPHEN] = flags;
+
+ lines[off + DIR] |= dir << DIR_SHIFT;
+ Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
+ // easy means all chars < the first RTL, so no emoji, no nothing
+ // XXX a run with no text or all spaces is easy but might be an empty
+ // RTL paragraph. Make sure easy is false if this is the case.
+ if (easy) {
+ mLineDirections[j] = linedirs;
+ } else {
+ mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs,
+ start - widthStart, end - start);
+ }
+
mLineCount++;
return v;
}
- private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
- TextPaint paint, boolean forceEllipsis, int dir) {
+ private void calculateEllipsis(int lineStart, int lineEnd,
+ float[] widths, int widthStart,
+ float avail, TextUtils.TruncateAt where,
+ int line, float textWidth, TextPaint paint,
+ boolean forceEllipsis) {
+ avail -= getTotalInsets(line);
if (textWidth <= avail && !forceEllipsis) {
// Everything fits!
mLines[mColumns * line + ELLIPSIS_START] = 0;
@@ -1079,47 +1081,11 @@
return;
}
- float tempAvail = avail;
- int numberOfTries = 0;
- boolean lineFits = false;
- do {
- final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
- widthStart, tempAvail, where, line, textWidth, paint, forceEllipsis, dir);
- if (ellipsizedWidth <= avail) {
- lineFits = true;
- } else {
- numberOfTries++;
- if (numberOfTries > 10) {
- // If the text still doesn't fit after ten tries, assume it will never fit and
- // ellipsize it all.
- mLines[mColumns * line + ELLIPSIS_START] = 0;
- mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart;
- lineFits = true;
- } else {
- // Some side effect of ellipsization has caused the text to go over the
- // available width. Let's make the available width shorter by exactly that
- // amount and retry.
- tempAvail -= ellipsizedWidth - avail;
- }
- }
- } while (!lineFits);
- mEllipsized = true;
- }
+ float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
+ int ellipsisStart = 0;
+ int ellipsisCount = 0;
+ int len = lineEnd - lineStart;
- // Returns the width of the ellipsized line which in some rare cases can actually be larger
- // than 'avail' (due to kerning or other context-based effect of replacement of text by
- // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode,
- // returns 0 so the caller can stop iterating.
- private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
- int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
- TextPaint paint, boolean forceEllipsis, int dir) {
- final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where));
- final int ellipsisStart;
- final int ellipsisCount;
- final int len = lineEnd - lineStart;
- final int offset = lineStart - widthStart;
-
- int hyphen = getHyphen(line);
// We only support start ellipsis on a single line
if (where == TextUtils.TruncateAt.START) {
if (mMaximumVisibleLineCount == 1) {
@@ -1127,9 +1093,9 @@
int i;
for (i = len; i > 0; i--) {
- final float w = widths[i - 1 + offset];
+ float w = widths[i - 1 + lineStart - widthStart];
if (w + sum + ellipsisWidth > avail) {
- while (i < len && widths[i + offset] == 0.0f) {
+ while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
i++;
}
break;
@@ -1140,13 +1106,9 @@
ellipsisStart = 0;
ellipsisCount = i;
- // Strip the potential hyphenation at beginning of line.
- hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE;
} else {
- ellipsisStart = 0;
- ellipsisCount = 0;
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "Start ellipsis only supported with one line");
+ Log.w(TAG, "Start Ellipsis only supported with one line");
}
}
} else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE ||
@@ -1155,7 +1117,7 @@
int i;
for (i = 0; i < len; i++) {
- final float w = widths[i + offset];
+ float w = widths[i + lineStart - widthStart];
if (w + sum + ellipsisWidth > avail) {
break;
@@ -1164,27 +1126,24 @@
sum += w;
}
- if (forceEllipsis && i == len && len > 0) {
+ ellipsisStart = i;
+ ellipsisCount = len - i;
+ if (forceEllipsis && ellipsisCount == 0 && len > 0) {
ellipsisStart = len - 1;
ellipsisCount = 1;
- } else {
- ellipsisStart = i;
- ellipsisCount = len - i;
}
- // Strip the potential hyphenation at end of line.
- hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE;
- } else { // where = TextUtils.TruncateAt.MIDDLE
- // We only support middle ellipsis on a single line.
+ } else {
+ // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line
if (mMaximumVisibleLineCount == 1) {
float lsum = 0, rsum = 0;
int left = 0, right = len;
- final float ravail = (avail - ellipsisWidth) / 2;
+ float ravail = (avail - ellipsisWidth) / 2;
for (right = len; right > 0; right--) {
- final float w = widths[right - 1 + offset];
+ float w = widths[right - 1 + lineStart - widthStart];
if (w + rsum > ravail) {
- while (right < len && widths[right + offset] == 0.0f) {
+ while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
right++;
}
break;
@@ -1192,9 +1151,9 @@
rsum += w;
}
- final float lavail = avail - ellipsisWidth - rsum;
+ float lavail = avail - ellipsisWidth - rsum;
for (left = 0; left < right; left++) {
- final float w = widths[left + offset];
+ float w = widths[left + lineStart - widthStart];
if (w + lsum > lavail) {
break;
@@ -1206,53 +1165,14 @@
ellipsisStart = left;
ellipsisCount = right - left;
} else {
- ellipsisStart = 0;
- ellipsisCount = 0;
if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "Middle ellipsis only supported with one line");
+ Log.w(TAG, "Middle Ellipsis only supported with one line");
}
}
}
+ mEllipsized = true;
mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart;
mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount;
-
- if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) {
- // Unsupported ellipsization mode or all text is ellipsized away. Return 0.
- return 0.0f;
- }
-
- final boolean isSpanned = text instanceof Spanned;
- final Ellipsizer ellipsizedText = isSpanned
- ? new SpannedEllipsizer(text)
- : new Ellipsizer(text);
- ellipsizedText.mLayout = this;
- ellipsizedText.mMethod = where;
-
- final boolean hasTabs = getLineContainsTab(line);
- final TabStops tabStops;
- if (hasTabs && isSpanned) {
- final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart,
- lineEnd, TabStopSpan.class);
- if (tabs.length == 0) {
- tabStops = null;
- } else {
- tabStops = new TabStops(TAB_INCREMENT, tabs);
- }
- } else {
- tabStops = null;
- }
- final TextLine textline = TextLine.obtain();
- paint.setHyphenEdit(hyphen);
- textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line),
- hasTabs, tabStops);
- // Since TextLine.metric() returns negative values for RTL text, multiplication by dir
- // converts it to an actual width. Note that we don't want to use the absolute value,
- // since we may actually have glyphs with negative advances, which by definition always
- // fit.
- final float ellipsizedWidth = textline.metrics(null) * dir;
- paint.setHyphenEdit(0);
- TextLine.recycle(textline);
- return ellipsizedWidth;
}
private float getTotalInsets(int line) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 463a986..979c43c 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -89,8 +89,8 @@
/** {@hide} */
@NonNull
- public static String getEllipsisString(@NonNull TruncateAt method) {
- return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
+ public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) {
+ return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL;
}
@@ -1187,11 +1187,9 @@
* or, if it does not fit, a truncated
* copy with ellipsis character added at the specified edge or center.
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint p,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where) {
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint p,
+ float avail, TruncateAt where) {
return ellipsize(text, p, avail, where, false, null);
}
@@ -1207,11 +1205,9 @@
* report the start and end of the ellipsized range. TextDirection
* is determined by the first strong directional character.
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where,
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint paint,
+ float avail, TruncateAt where,
boolean preserveLength,
@Nullable EllipsizeCallback callback) {
return ellipsize(text, paint, avail, where, preserveLength, callback,
@@ -1232,19 +1228,16 @@
*
* @hide
*/
- @NonNull
- public static CharSequence ellipsize(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @FloatRange(from = 0.0) float avail,
- @NonNull TruncateAt where,
+ public static CharSequence ellipsize(CharSequence text,
+ TextPaint paint,
+ float avail, TruncateAt where,
boolean preserveLength,
@Nullable EllipsizeCallback callback,
- @NonNull TextDirectionHeuristic textDir,
- @NonNull String ellipsis) {
+ TextDirectionHeuristic textDir, String ellipsis) {
- final int len = text.length();
- final MeasuredText mt = MeasuredText.obtain();
- MeasuredText resultMt = null;
+ int len = text.length();
+
+ MeasuredText mt = MeasuredText.obtain();
try {
float width = setPara(mt, paint, text, 0, text.length(), textDir);
@@ -1252,107 +1245,74 @@
if (callback != null) {
callback.ellipsized(0, 0);
}
+
return text;
}
- resultMt = MeasuredText.obtain();
- // First estimate of effective width of ellipsis.
- float ellipsisWidth = paint.measureText(ellipsis);
- int numberOfTries = 0;
- boolean textFits = false;
- int start, end;
- CharSequence result;
- do {
- if (avail < ellipsisWidth) {
- // Even the ellipsis can't fit. So it all goes.
- start = 0;
- end = len;
- } else {
- final float remainingWidth = avail - ellipsisWidth;
- if (where == TruncateAt.START) {
- start = 0;
- end = len - mt.breakText(len, false /* backwards */, remainingWidth);
- } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
- start = mt.breakText(len, true /* forwards */, remainingWidth);
- end = len;
- } else {
- end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2);
- start = mt.breakText(end, true /* forwards */,
- remainingWidth - mt.measure(end, len));
- }
- }
+ // XXX assumes ellipsis string does not require shaping and
+ // is unaffected by style
+ float ellipsiswid = paint.measureText(ellipsis);
+ avail -= ellipsiswid;
- final char[] buf = mt.mChars;
- final Spanned sp = text instanceof Spanned ? (Spanned) text : null;
-
- final int removed = end - start;
- final int remaining = len - removed;
- if (preserveLength) {
- int pos = start;
- if (remaining > 0 && removed >= ellipsis.length()) {
- ellipsis.getChars(0, ellipsis.length(), buf, start);
- pos += ellipsis.length();
- } // else eliminate the ellipsis
- while (pos < end) {
- buf[pos++] = ELLIPSIS_FILLER;
- }
- final String s = new String(buf, 0, len);
- if (sp == null) {
- result = s;
- } else {
- final SpannableString ss = new SpannableString(s);
- copySpansFrom(sp, 0, len, Object.class, ss, 0);
- result = ss;
- }
- } else {
- if (remaining == 0) {
- result = "";
- } else if (sp == null) {
- final StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
- sb.append(buf, 0, start);
- sb.append(ellipsis);
- sb.append(buf, end, len - end);
- result = sb.toString();
- } else {
- final SpannableStringBuilder ssb = new SpannableStringBuilder();
- ssb.append(text, 0, start);
- ssb.append(ellipsis);
- ssb.append(text, end, len);
- result = ssb;
- }
- }
-
- if (remaining == 0) { // All text is gone.
- textFits = true;
- } else {
- width = setPara(resultMt, paint, result, 0, result.length(), textDir);
- if (width <= avail) {
- textFits = true;
- } else {
- numberOfTries++;
- if (numberOfTries > 10) {
- // If the text still doesn't fit after ten tries, assume it will never
- // fit and ellipsize it all. We do this by setting the width of the
- // ellipsis to be positive infinity, so we get to empty text in the next
- // round.
- ellipsisWidth = Float.POSITIVE_INFINITY;
- } else {
- // Adjust the width of the ellipsis by adding the amount 'width' is
- // still over.
- ellipsisWidth += width - avail;
- }
- }
- }
- } while (!textFits);
- if (callback != null) {
- callback.ellipsized(start, end);
+ int left = 0;
+ int right = len;
+ if (avail < 0) {
+ // it all goes
+ } else if (where == TruncateAt.START) {
+ right = len - mt.breakText(len, false, avail);
+ } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
+ left = mt.breakText(len, true, avail);
+ } else {
+ right = len - mt.breakText(len, false, avail / 2);
+ avail -= mt.measure(right, len);
+ left = mt.breakText(right, true, avail);
}
- return result;
+
+ if (callback != null) {
+ callback.ellipsized(left, right);
+ }
+
+ char[] buf = mt.mChars;
+ Spanned sp = text instanceof Spanned ? (Spanned) text : null;
+
+ final int removed = right - left;
+ final int remaining = len - removed;
+ if (preserveLength) {
+ if (remaining > 0 && removed >= ellipsis.length()) {
+ ellipsis.getChars(0, ellipsis.length(), buf, left);
+ left += ellipsis.length();
+ } // else skip the ellipsis
+ for (int i = left; i < right; i++) {
+ buf[i] = ELLIPSIS_FILLER;
+ }
+ String s = new String(buf, 0, len);
+ if (sp == null) {
+ return s;
+ }
+ SpannableString ss = new SpannableString(s);
+ copySpansFrom(sp, 0, len, Object.class, ss, 0);
+ return ss;
+ }
+
+ if (remaining == 0) {
+ return "";
+ }
+
+ if (sp == null) {
+ StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
+ sb.append(buf, 0, left);
+ sb.append(ellipsis);
+ sb.append(buf, right, len - right);
+ return sb.toString();
+ }
+
+ SpannableStringBuilder ssb = new SpannableStringBuilder();
+ ssb.append(text, 0, left);
+ ssb.append(ellipsis);
+ ssb.append(text, right, len);
+ return ssb;
} finally {
MeasuredText.recycle(mt);
- if (resultMt != null) {
- MeasuredText.recycle(resultMt);
- }
}
}
@@ -1383,6 +1343,7 @@
* @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
* doesn't fit, it will return an empty string.
*/
+
public static CharSequence listEllipsize(@Nullable Context context,
@Nullable List<CharSequence> elements, @NonNull String separator,
@NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 48e5ca9..af26a88 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -193,6 +193,8 @@
private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
+ View cycleCheck = userSetNextFocus;
+ boolean cycleStep = true; // we want the first toggle to yield false
while (userSetNextFocus != null) {
if (userSetNextFocus.isFocusable()
&& userSetNextFocus.getVisibility() == View.VISIBLE
@@ -201,6 +203,14 @@
return userSetNextFocus;
}
userSetNextFocus = userSetNextFocus.findUserSetNextFocus(root, direction);
+ if (cycleStep = !cycleStep) {
+ cycleCheck = cycleCheck.findUserSetNextFocus(root, direction);
+ if (cycleCheck == userSetNextFocus) {
+ // found a cycle, user-specified focus forms a loop and none of the views
+ // are currently focusable.
+ break;
+ }
+ }
}
return null;
}
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 20ab539..c566a65 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -111,9 +111,10 @@
* to indicate whether the event was handled. No new input events will be received
* until {@link #finishInputEvent} is called.
*
+ * @param displayId The display id on which input event triggered.
* @param event The input event that was received.
*/
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
finishInputEvent(event, false);
}
@@ -180,9 +181,9 @@
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchInputEvent(int seq, InputEvent event) {
+ private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
mSeqMap.put(event.getSequenceNumber(), seq);
- onInputEvent(event);
+ onInputEvent(event, displayId);
}
// Called from native code.
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 22329f4..44c88e1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -272,6 +272,15 @@
public static final int POWER_MODE_DOZE_SUSPEND = 3;
/**
+ * A value for windowType used to indicate that the window should be omitted from screenshots
+ * and display mirroring. A temporary workaround until we express such things with
+ * the hierarchy.
+ * TODO: b/64227542
+ * @hide
+ */
+ public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
+
+ /**
* Create a surface with a name.
* <p>
* The surface creation flags specify what kind of surface to create and
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 05f9da5..6bf4845 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6752,7 +6752,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 4041bcf..be763af 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1413,11 +1413,12 @@
public static final int PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 0x00080000;
/**
- * Flag to indicate that this window should be ignored when determining what parts of the
- * screen can be magnified.
+ * Indicates that this window is the rounded corners overlay present on some
+ * devices this means that it will be excluded from: screenshots,
+ * screen magnification, and mirroring.
* @hide
*/
- public static final int PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT = 0x00100000;
+ public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000;
/**
* Control flags that are private to the platform.
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 4c9cf40..98f8dc8 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -347,4 +347,11 @@
* Requests the window manager to recompute the windows for accessibility.
*/
public abstract void computeWindowsForAccessibility();
+
+ /**
+ * Called after virtual display Id is updated by
+ * {@link com.android.server.vr.Vr2dDisplay} with a specific
+ * {@param vr2dDisplayId}.
+ */
+ public abstract void setVr2dDisplayId(int vr2dDisplayId);
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 668d25e..49b7ed8 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -616,7 +617,16 @@
* 2. motionEvent will be recycled after onPointerEvent returns so if it is needed later a
* copy() must be made and the copy must be recycled.
**/
- public void onPointerEvent(MotionEvent motionEvent);
+ void onPointerEvent(MotionEvent motionEvent);
+
+ /**
+ * @see #onPointerEvent(MotionEvent)
+ **/
+ default void onPointerEvent(MotionEvent motionEvent, int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ onPointerEvent(motionEvent);
+ }
+ }
}
/** Window has been added to the screen. */
diff --git a/core/java/android/view/textclassifier/LangId.java b/core/java/android/view/textclassifier/LangId.java
deleted file mode 100644
index 23c7842..0000000
--- a/core/java/android/view/textclassifier/LangId.java
+++ /dev/null
@@ -1,69 +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 android.view.textclassifier;
-
-/**
- * Java wrapper for LangId native library interface.
- * This class is used to detect languages in text.
- */
-final class LangId {
-
- static {
- System.loadLibrary("textclassifier");
- }
-
- private final long mModelPtr;
-
- /**
- * Creates a new instance of LangId predictor, using the provided model image.
- */
- LangId(int fd) {
- mModelPtr = nativeNew(fd);
- }
-
- /**
- * Detects the language for given text.
- */
- public ClassificationResult[] findLanguages(String text) {
- return nativeFindLanguages(mModelPtr, text);
- }
-
- /**
- * Frees up the allocated memory.
- */
- public void close() {
- nativeClose(mModelPtr);
- }
-
- private static native long nativeNew(int fd);
-
- private static native ClassificationResult[] nativeFindLanguages(
- long context, String text);
-
- private static native void nativeClose(long context);
-
- /** Classification result for findLanguage method. */
- static final class ClassificationResult {
- final String mLanguage;
- /** float range: 0 - 1 */
- final float mScore;
-
- ClassificationResult(String language, float score) {
- mLanguage = language;
- mScore = score;
- }
- }
-}
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index efc88e2..d7b0776 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -16,37 +16,22 @@
package android.view.textclassifier;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
import com.android.internal.util.Preconditions;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
/**
* Interface to the text classification service.
*/
@SystemService(Context.TEXT_CLASSIFICATION_SERVICE)
public final class TextClassificationManager {
- private static final String LOG_TAG = "TextClassificationManager";
-
private final Object mTextClassifierLock = new Object();
- private final Object mLangIdLock = new Object();
private final Context mContext;
- private ParcelFileDescriptor mLangIdFd;
private TextClassifier mTextClassifier;
- private LangId mLangId;
/** @hide */
public TextClassificationManager(Context context) {
@@ -75,47 +60,4 @@
mTextClassifier = textClassifier;
}
}
-
- /**
- * Returns information containing languages that were detected in the provided text.
- * This is a blocking operation you should avoid calling it on the UI thread.
- *
- * @throws IllegalArgumentException if text is null
- * @hide
- */
- public List<TextLanguage> detectLanguages(@NonNull CharSequence text) {
- Preconditions.checkArgument(text != null);
- try {
- if (text.length() > 0) {
- final LangId.ClassificationResult[] results =
- getLanguageDetector().findLanguages(text.toString());
- final TextLanguage.Builder tlBuilder = new TextLanguage.Builder(0, text.length());
- final int size = results.length;
- for (int i = 0; i < size; i++) {
- tlBuilder.setLanguage(
- new Locale.Builder().setLanguageTag(results[i].mLanguage).build(),
- results[i].mScore);
- }
-
- return Collections.unmodifiableList(Arrays.asList(tlBuilder.build()));
- }
- } catch (Throwable t) {
- // Avoid throwing from this method. Log the error.
- Log.e(LOG_TAG, "Error detecting languages for text. Returning empty result.", t);
- }
- // Getting here means something went wrong. Return an empty result.
- return Collections.emptyList();
- }
-
- private LangId getLanguageDetector() throws FileNotFoundException {
- synchronized (mLangIdLock) {
- if (mLangId == null) {
- mLangIdFd = ParcelFileDescriptor.open(
- new File("/etc/textclassifier/textclassifier.langid.model"),
- ParcelFileDescriptor.MODE_READ_ONLY);
- mLangId = new LangId(mLangIdFd.getFd());
- }
- return mLangId;
- }
- }
}
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
deleted file mode 100644
index 209813a..0000000
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ /dev/null
@@ -1,140 +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 android.view.textclassifier;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Specifies detected languages for a section of text indicated by a start and end index.
- * @hide
- */
-public final class TextLanguage {
-
- private final int mStartIndex;
- private final int mEndIndex;
- @NonNull private final EntityConfidence<Locale> mLanguageConfidence;
- @NonNull private final List<Locale> mLanguages;
-
- private TextLanguage(
- int startIndex, int endIndex, @NonNull EntityConfidence<Locale> languageConfidence) {
- mStartIndex = startIndex;
- mEndIndex = endIndex;
- mLanguageConfidence = new EntityConfidence<>(languageConfidence);
- mLanguages = mLanguageConfidence.getEntities();
- }
-
- /**
- * Returns the start index of the detected languages in the text provided to generate this
- * object.
- */
- public int getStartIndex() {
- return mStartIndex;
- }
-
- /**
- * Returns the end index of the detected languages in the text provided to generate this object.
- */
- public int getEndIndex() {
- return mEndIndex;
- }
-
- /**
- * Returns the number of languages found in the classified text.
- */
- @IntRange(from = 0)
- public int getLanguageCount() {
- return mLanguages.size();
- }
-
- /**
- * Returns the language locale at the specified index.
- * Language locales are ordered from high confidence to low confidence.
- *
- * @throws IndexOutOfBoundsException if the specified index is out of range.
- * @see #getLanguageCount() for the number of language locales available.
- */
- @NonNull
- public Locale getLanguage(int index) {
- return mLanguages.get(index);
- }
-
- /**
- * Returns the confidence score for the specified language. The value ranges from
- * 0 (low confidence) to 1 (high confidence). 0 indicates that the language was
- * not found for the classified text.
- */
- @FloatRange(from = 0.0, to = 1.0)
- public float getConfidenceScore(@Nullable Locale language) {
- return mLanguageConfidence.getConfidenceScore(language);
- }
-
- @Override
- public String toString() {
- return String.format("TextLanguage {%d, %d, %s}",
- mStartIndex, mEndIndex, mLanguageConfidence);
- }
-
- /**
- * Builder to build {@link TextLanguage} objects.
- */
- public static final class Builder {
-
- private final int mStartIndex;
- private final int mEndIndex;
- @NonNull private final EntityConfidence<Locale> mLanguageConfidence =
- new EntityConfidence<>();
-
- /**
- * Creates a builder to build {@link TextLanguage} objects.
- *
- * @param startIndex the start index of the detected languages in the text provided
- * to generate the result
- * @param endIndex the end index of the detected languages in the text provided
- * to generate the result. Must be greater than startIndex
- */
- public Builder(@IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex) {
- Preconditions.checkArgument(startIndex >= 0);
- Preconditions.checkArgument(endIndex > startIndex);
- mStartIndex = startIndex;
- mEndIndex = endIndex;
- }
-
- /**
- * Sets a language locale with the associated confidence score.
- */
- public Builder setLanguage(
- @NonNull Locale locale, @FloatRange(from = 0.0, to = 1.0) float confidenceScore) {
- mLanguageConfidence.setEntityType(locale, confidenceScore);
- return this;
- }
-
- /**
- * Builds and returns a {@link TextLanguage}.
- */
- public TextLanguage build() {
- return new TextLanguage(mStartIndex, mEndIndex, mLanguageConfidence);
- }
- }
-}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 1291671..bbfa6e0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -119,6 +119,7 @@
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
+import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.ActionMode;
@@ -855,22 +856,9 @@
mTransformation = null;
- int textColorHighlight = 0;
- ColorStateList textColor = null;
- ColorStateList textColorHint = null;
- ColorStateList textColorLink = null;
- int textSize = 15;
- String fontFamily = null;
- Typeface fontTypeface = null;
- boolean fontFamilyExplicit = false;
- int typefaceIndex = -1;
- int styleIndex = -1;
- boolean allCaps = false;
- int shadowcolor = 0;
- float dx = 0, dy = 0, r = 0;
- boolean elegant = false;
- float letterSpacing = 0;
- String fontFeatureSettings = null;
+ final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
+ attributes.mTextColor = ColorStateList.valueOf(0xFF000000);
+ attributes.mTextSize = 15;
mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
@@ -894,87 +882,8 @@
ap, com.android.internal.R.styleable.TextAppearance);
}
if (appearance != null) {
- int n = appearance.getIndexCount();
- for (int i = 0; i < n; i++) {
- int attr = appearance.getIndex(i);
-
- switch (attr) {
- case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
- textColorHighlight = appearance.getColor(attr, textColorHighlight);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textColor:
- textColor = appearance.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textColorHint:
- textColorHint = appearance.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textColorLink:
- textColorLink = appearance.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textSize:
- textSize = appearance.getDimensionPixelSize(attr, textSize);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_typeface:
- typefaceIndex = appearance.getInt(attr, -1);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_fontFamily:
- if (!context.isRestricted() && context.canLoadUnsafeResources()) {
- try {
- fontTypeface = appearance.getFont(attr);
- } catch (UnsupportedOperationException
- | Resources.NotFoundException e) {
- // Expected if it is not a font resource.
- }
- }
- if (fontTypeface == null) {
- fontFamily = appearance.getString(attr);
- }
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textStyle:
- styleIndex = appearance.getInt(attr, -1);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_textAllCaps:
- allCaps = appearance.getBoolean(attr, false);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_shadowColor:
- shadowcolor = appearance.getInt(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_shadowDx:
- dx = appearance.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_shadowDy:
- dy = appearance.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_shadowRadius:
- r = appearance.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
- elegant = appearance.getBoolean(attr, false);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_letterSpacing:
- letterSpacing = appearance.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
- fontFeatureSettings = appearance.getString(attr);
- break;
- }
- }
-
+ readTextAppearance(context, appearance, attributes, false /* styleArray */);
+ attributes.mFontFamilyExplicit = false;
appearance.recycle();
}
@@ -1005,6 +914,8 @@
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
+ readTextAppearance(context, a, attributes, true /* styleArray */);
+
int n = a.getIndexCount();
boolean fromResourceId = false;
@@ -1195,69 +1106,10 @@
mFreezesText = a.getBoolean(attr, false);
break;
- case com.android.internal.R.styleable.TextView_shadowColor:
- shadowcolor = a.getInt(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextView_shadowDx:
- dx = a.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextView_shadowDy:
- dy = a.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextView_shadowRadius:
- r = a.getFloat(attr, 0);
- break;
-
case com.android.internal.R.styleable.TextView_enabled:
setEnabled(a.getBoolean(attr, isEnabled()));
break;
- case com.android.internal.R.styleable.TextView_textColorHighlight:
- textColorHighlight = a.getColor(attr, textColorHighlight);
- break;
-
- case com.android.internal.R.styleable.TextView_textColor:
- textColor = a.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextView_textColorHint:
- textColorHint = a.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextView_textColorLink:
- textColorLink = a.getColorStateList(attr);
- break;
-
- case com.android.internal.R.styleable.TextView_textSize:
- textSize = a.getDimensionPixelSize(attr, textSize);
- break;
-
- case com.android.internal.R.styleable.TextView_typeface:
- typefaceIndex = a.getInt(attr, typefaceIndex);
- break;
-
- case com.android.internal.R.styleable.TextView_textStyle:
- styleIndex = a.getInt(attr, styleIndex);
- break;
-
- case com.android.internal.R.styleable.TextView_fontFamily:
- if (!context.isRestricted() && context.canLoadUnsafeResources()) {
- try {
- fontTypeface = a.getFont(attr);
- } catch (UnsupportedOperationException | Resources.NotFoundException e) {
- // Expected if it is not a resource reference or if it is a reference to
- // another resource type.
- }
- }
- if (fontTypeface == null) {
- fontFamily = a.getString(attr);
- }
- fontFamilyExplicit = true;
- break;
-
case com.android.internal.R.styleable.TextView_password:
password = a.getBoolean(attr, password);
break;
@@ -1345,22 +1197,6 @@
setTextIsSelectable(a.getBoolean(attr, false));
break;
- case com.android.internal.R.styleable.TextView_textAllCaps:
- allCaps = a.getBoolean(attr, false);
- break;
-
- case com.android.internal.R.styleable.TextView_elegantTextHeight:
- elegant = a.getBoolean(attr, false);
- break;
-
- case com.android.internal.R.styleable.TextView_letterSpacing:
- letterSpacing = a.getFloat(attr, 0);
- break;
-
- case com.android.internal.R.styleable.TextView_fontFeatureSettings:
- fontFeatureSettings = a.getString(attr);
- break;
-
case com.android.internal.R.styleable.TextView_breakStrategy:
mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
break;
@@ -1592,38 +1428,20 @@
break;
}
- setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
- setHintTextColor(textColorHint);
- setLinkTextColor(textColorLink);
- if (textColorHighlight != 0) {
- setHighlightColor(textColorHighlight);
- }
- setRawTextSize(textSize, true /* shouldRequestLayout */);
- setElegantTextHeight(elegant);
- setLetterSpacing(letterSpacing);
- setFontFeatureSettings(fontFeatureSettings);
-
- if (allCaps) {
- setTransformationMethod(new AllCapsTransformationMethod(getContext()));
- }
-
- if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
- setTransformationMethod(PasswordTransformationMethod.getInstance());
- typefaceIndex = MONOSPACE;
- } else if (mEditor != null
+ final boolean isPassword = password || passwordInputType || webPasswordInputType
+ || numberPasswordInputType;
+ final boolean isMonospaceEnforced = isPassword || (mEditor != null
&& (mEditor.mInputType
- & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
- == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
- typefaceIndex = MONOSPACE;
+ & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD));
+ if (isMonospaceEnforced) {
+ attributes.mTypefaceIndex = MONOSPACE;
}
- if (typefaceIndex != -1 && !fontFamilyExplicit) {
- fontFamily = null;
- }
- setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
+ applyTextAppearance(attributes);
- if (shadowcolor != 0) {
- setShadowLayer(r, dx, dy, shadowcolor);
+ if (isPassword) {
+ setTransformationMethod(PasswordTransformationMethod.getInstance());
}
if (maxlength >= 0) {
@@ -3397,79 +3215,244 @@
@Deprecated
public void setTextAppearance(Context context, @StyleRes int resId) {
final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
+ final TextAppearanceAttributes attributes = new TextAppearanceAttributes();
+ readTextAppearance(context, ta, attributes, false /* styleArray */);
+ ta.recycle();
+ applyTextAppearance(attributes);
+ }
- final int textColorHighlight = ta.getColor(
- R.styleable.TextAppearance_textColorHighlight, 0);
- if (textColorHighlight != 0) {
- setHighlightColor(textColorHighlight);
+ /**
+ * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code
+ * that reads these attributes in the constructor and in {@link #setTextAppearance}.
+ */
+ private static class TextAppearanceAttributes {
+ int mTextColorHighlight = 0;
+ ColorStateList mTextColor = null;
+ ColorStateList mTextColorHint = null;
+ ColorStateList mTextColorLink = null;
+ int mTextSize = 0;
+ String mFontFamily = null;
+ Typeface mFontTypeface = null;
+ boolean mFontFamilyExplicit = false;
+ int mTypefaceIndex = -1;
+ int mStyleIndex = -1;
+ boolean mAllCaps = false;
+ int mShadowColor = 0;
+ float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
+ boolean mHasElegant = false;
+ boolean mElegant = false;
+ boolean mHasLetterSpacing = false;
+ float mLetterSpacing = 0;
+ String mFontFeatureSettings = null;
+
+ @Override
+ public String toString() {
+ return "TextAppearanceAttributes {\n"
+ + " mTextColorHighlight:" + mTextColorHighlight + "\n"
+ + " mTextColor:" + mTextColor + "\n"
+ + " mTextColorHint:" + mTextColorHint + "\n"
+ + " mTextColorLink:" + mTextColorLink + "\n"
+ + " mTextSize:" + mTextSize + "\n"
+ + " mFontFamily:" + mFontFamily + "\n"
+ + " mFontTypeface:" + mFontTypeface + "\n"
+ + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n"
+ + " mTypefaceIndex:" + mTypefaceIndex + "\n"
+ + " mStyleIndex:" + mStyleIndex + "\n"
+ + " mAllCaps:" + mAllCaps + "\n"
+ + " mShadowColor:" + mShadowColor + "\n"
+ + " mShadowDx:" + mShadowDx + "\n"
+ + " mShadowDy:" + mShadowDy + "\n"
+ + " mShadowRadius:" + mShadowRadius + "\n"
+ + " mHasElegant:" + mHasElegant + "\n"
+ + " mElegant:" + mElegant + "\n"
+ + " mHasLetterSpacing:" + mHasLetterSpacing + "\n"
+ + " mLetterSpacing:" + mLetterSpacing + "\n"
+ + " mFontFeatureSettings:" + mFontFeatureSettings + "\n"
+ + "}";
}
+ }
- final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
- if (textColor != null) {
- setTextColor(textColor);
- }
+ // Maps styleable attributes that exist both in TextView style and TextAppearance.
+ private static final SparseIntArray sAppearanceValues = new SparseIntArray();
+ static {
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight,
+ com.android.internal.R.styleable.TextAppearance_textColorHighlight);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor,
+ com.android.internal.R.styleable.TextAppearance_textColor);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint,
+ com.android.internal.R.styleable.TextAppearance_textColorHint);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink,
+ com.android.internal.R.styleable.TextAppearance_textColorLink);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize,
+ com.android.internal.R.styleable.TextAppearance_textSize);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface,
+ com.android.internal.R.styleable.TextAppearance_typeface);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily,
+ com.android.internal.R.styleable.TextAppearance_fontFamily);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle,
+ com.android.internal.R.styleable.TextAppearance_textStyle);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps,
+ com.android.internal.R.styleable.TextAppearance_textAllCaps);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor,
+ com.android.internal.R.styleable.TextAppearance_shadowColor);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx,
+ com.android.internal.R.styleable.TextAppearance_shadowDx);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy,
+ com.android.internal.R.styleable.TextAppearance_shadowDy);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius,
+ com.android.internal.R.styleable.TextAppearance_shadowRadius);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
+ com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
+ com.android.internal.R.styleable.TextAppearance_letterSpacing);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
+ com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
+ }
- final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
- if (textSize != 0) {
- setRawTextSize(textSize, true /* shouldRequestLayout */);
- }
-
- final ColorStateList textColorHint = ta.getColorStateList(
- R.styleable.TextAppearance_textColorHint);
- if (textColorHint != null) {
- setHintTextColor(textColorHint);
- }
-
- final ColorStateList textColorLink = ta.getColorStateList(
- R.styleable.TextAppearance_textColorLink);
- if (textColorLink != null) {
- setLinkTextColor(textColorLink);
- }
-
- Typeface fontTypeface = null;
- String fontFamily = null;
- if (!context.isRestricted() && context.canLoadUnsafeResources()) {
- try {
- fontTypeface = ta.getFont(R.styleable.TextAppearance_fontFamily);
- } catch (UnsupportedOperationException | Resources.NotFoundException e) {
- // Expected if it is not a font resource.
+ /**
+ * Read the Text Appearance attributes from a given TypedArray and set its values to the given
+ * set. If the TypedArray contains a value that was already set in the given attributes, that
+ * will be overriden.
+ *
+ * @param context The Context to be used
+ * @param appearance The TypedArray to read properties from
+ * @param attributes the TextAppearanceAttributes to fill in
+ * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines
+ * what attribute indexes will be used to read the properties.
+ */
+ private void readTextAppearance(Context context, TypedArray appearance,
+ TextAppearanceAttributes attributes, boolean styleArray) {
+ final int n = appearance.getIndexCount();
+ for (int i = 0; i < n; i++) {
+ final int attr = appearance.getIndex(i);
+ int index = attr;
+ // Translate style array index ids to TextAppearance ids.
+ if (styleArray) {
+ index = sAppearanceValues.get(attr, -1);
+ if (index == -1) {
+ // This value is not part of a Text Appearance and should be ignored.
+ continue;
+ }
+ }
+ switch (index) {
+ case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
+ attributes.mTextColorHighlight =
+ appearance.getColor(attr, attributes.mTextColorHighlight);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textColor:
+ attributes.mTextColor = appearance.getColorStateList(attr);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textColorHint:
+ attributes.mTextColorHint = appearance.getColorStateList(attr);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textColorLink:
+ attributes.mTextColorLink = appearance.getColorStateList(attr);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textSize:
+ attributes.mTextSize =
+ appearance.getDimensionPixelSize(attr, attributes.mTextSize);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_typeface:
+ attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
+ if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
+ attributes.mFontFamily = null;
+ }
+ break;
+ case com.android.internal.R.styleable.TextAppearance_fontFamily:
+ if (!context.isRestricted() && context.canLoadUnsafeResources()) {
+ try {
+ attributes.mFontTypeface = appearance.getFont(attr);
+ } catch (UnsupportedOperationException | Resources.NotFoundException e) {
+ // Expected if it is not a font resource.
+ }
+ }
+ if (attributes.mFontTypeface == null) {
+ attributes.mFontFamily = appearance.getString(attr);
+ }
+ attributes.mFontFamilyExplicit = true;
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textStyle:
+ attributes.mStyleIndex = appearance.getInt(attr, attributes.mStyleIndex);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_textAllCaps:
+ attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_shadowColor:
+ attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_shadowDx:
+ attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_shadowDy:
+ attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_shadowRadius:
+ attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
+ attributes.mHasElegant = true;
+ attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_letterSpacing:
+ attributes.mHasLetterSpacing = true;
+ attributes.mLetterSpacing =
+ appearance.getFloat(attr, attributes.mLetterSpacing);
+ break;
+ case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
+ attributes.mFontFeatureSettings = appearance.getString(attr);
+ break;
+ default:
}
}
- if (fontTypeface == null) {
- fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
- }
- final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
- final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
- setTypefaceFromAttrs(fontTypeface, fontFamily, typefaceIndex, styleIndex);
+ }
- final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
- if (shadowColor != 0) {
- final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
- final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
- final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
- setShadowLayer(r, dx, dy, shadowColor);
+ private void applyTextAppearance(TextAppearanceAttributes attributes) {
+ if (attributes.mTextColor != null) {
+ setTextColor(attributes.mTextColor);
}
- if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
+ if (attributes.mTextColorHint != null) {
+ setHintTextColor(attributes.mTextColorHint);
+ }
+
+ if (attributes.mTextColorLink != null) {
+ setLinkTextColor(attributes.mTextColorLink);
+ }
+
+ if (attributes.mTextColorHighlight != 0) {
+ setHighlightColor(attributes.mTextColorHighlight);
+ }
+
+ if (attributes.mTextSize != 0) {
+ setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */);
+ }
+
+ if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
+ attributes.mFontFamily = null;
+ }
+ setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
+ attributes.mTypefaceIndex, attributes.mStyleIndex);
+
+ if (attributes.mShadowColor != 0) {
+ setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy,
+ attributes.mShadowColor);
+ }
+
+ if (attributes.mAllCaps) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
}
- if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
- setElegantTextHeight(ta.getBoolean(
- R.styleable.TextAppearance_elegantTextHeight, false));
+ if (attributes.mHasElegant) {
+ setElegantTextHeight(attributes.mElegant);
}
- if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
- setLetterSpacing(ta.getFloat(
- R.styleable.TextAppearance_letterSpacing, 0));
+ if (attributes.mHasLetterSpacing) {
+ setLetterSpacing(attributes.mLetterSpacing);
}
- if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
- setFontFeatureSettings(ta.getString(
- R.styleable.TextAppearance_fontFeatureSettings));
+ if (attributes.mFontFeatureSettings != null) {
+ setFontFeatureSettings(attributes.mFontFeatureSettings);
}
-
- ta.recycle();
}
/**
@@ -3744,6 +3727,8 @@
*
* @param elegant set the paint's elegant metrics flag.
*
+ * @see Paint#isElegantTextHeight(boolean)
+ *
* @attr ref android.R.styleable#TextView_elegantTextHeight
*/
public void setElegantTextHeight(boolean elegant) {
@@ -3758,6 +3743,19 @@
}
/**
+ * Get the value of the TextView's elegant height metrics flag. This setting selects font
+ * variants that have not been compacted to fit Latin-based vertical
+ * metrics, and also increases top and bottom bounds to provide more space.
+ * @return {@code true} if the elegant height metrics flag is set.
+ *
+ * @see #setElegantTextHeight(boolean)
+ * @see Paint#setElegantTextHeight(boolean)
+ */
+ public boolean isElegantTextHeight() {
+ return mTextPaint.isElegantTextHeight();
+ }
+
+ /**
* Gets the text letter-space value, which determines the spacing between characters.
* The value returned is in ems. Normally, this value is 0.0.
* @return The text letter-space value in ems.
@@ -4946,20 +4944,21 @@
private void updateTextColors() {
boolean inval = false;
- int color = mTextColor.getColorForState(getDrawableState(), 0);
+ final int[] drawableState = getDrawableState();
+ int color = mTextColor.getColorForState(drawableState, 0);
if (color != mCurTextColor) {
mCurTextColor = color;
inval = true;
}
if (mLinkTextColor != null) {
- color = mLinkTextColor.getColorForState(getDrawableState(), 0);
+ color = mLinkTextColor.getColorForState(drawableState, 0);
if (color != mTextPaint.linkColor) {
mTextPaint.linkColor = color;
inval = true;
}
}
if (mHintTextColor != null) {
- color = mHintTextColor.getColorForState(getDrawableState(), 0);
+ color = mHintTextColor.getColorForState(drawableState, 0);
if (color != mCurHintTextColor) {
mCurHintTextColor = color;
if (mText.length() == 0) {
@@ -8348,7 +8347,9 @@
}
private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
- final CharSequence text = getText();
+ final CharSequence text = mTransformed != null
+ ? mTransformed
+ : getText();
final int maxLines = getMaxLines();
if (mTempTextPaint == null) {
mTempTextPaint = new TextPaint();
@@ -9022,6 +9023,20 @@
}
/**
+ *
+ * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This
+ * settings is internally ignored if this field is editable or selectable.
+ * @return Whether the current transformation method is for ALL CAPS.
+ *
+ * @see #setAllCaps(boolean)
+ * @see #setTransformationMethod(TransformationMethod)
+ */
+ public boolean isAllCaps() {
+ final TransformationMethod method = getTransformationMethod();
+ return method != null && method instanceof AllCapsTransformationMethod;
+ }
+
+ /**
* If true, sets the properties of this field (number of lines, horizontally scrolling,
* transformation method) to be for a single-line input; if false, restores these to the default
* conditions.
diff --git a/core/java/com/android/internal/colorextraction/ColorExtractor.java b/core/java/com/android/internal/colorextraction/ColorExtractor.java
index 727412b..ef98a5e9 100644
--- a/core/java/com/android/internal/colorextraction/ColorExtractor.java
+++ b/core/java/com/android/internal/colorextraction/ColorExtractor.java
@@ -22,6 +22,7 @@
import android.app.WallpaperManager;
import android.content.Context;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
@@ -44,6 +45,7 @@
private static final int[] sGradientTypes = new int[]{TYPE_NORMAL, TYPE_DARK, TYPE_EXTRA_DARK};
private static final String TAG = "ColorExtractor";
+ private static final boolean DEBUG = false;
protected final SparseArray<GradientColors[]> mGradientColors;
private final ArrayList<WeakReference<OnColorsChangedListener>> mOnColorsChangedListeners;
@@ -147,6 +149,9 @@
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
+ if (DEBUG) {
+ Log.d(TAG, "New wallpaper colors for " + which + ": " + colors);
+ }
boolean changed = false;
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
mLockColors = colors;
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index d765fd2..d49d572 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -38,6 +38,7 @@
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
+import android.provider.MetadataReader;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -51,6 +52,7 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -118,26 +120,31 @@
if (!file.isFile()) {
Log.w(TAG, "Can't stream non-regular file. Returning empty metadata.");
- return Bundle.EMPTY;
+ return null;
}
if (!file.canRead()) {
Log.w(TAG, "Can't stream non-readable file. Returning empty metadata.");
- return Bundle.EMPTY;
+ return null;
}
- String filePath = file.getAbsolutePath();
- FileInputStream stream = new FileInputStream(filePath);
+ String mimeType = getTypeForFile(file);
+ if (!MetadataReader.isSupportedMimeType(mimeType)) {
+ return null;
+ }
+ InputStream stream = null;
try {
- return getDocumentMetadataFromStream(stream, getTypeForFile(file));
+ Bundle metadata = new Bundle();
+ stream = new FileInputStream(file.getAbsolutePath());
+ MetadataReader.getMetadata(metadata, stream, mimeType, null);
+ return metadata;
} catch (IOException e) {
Log.e(TAG, "An error occurred retrieving the metadata", e);
+ return null;
} finally {
IoUtils.closeQuietly(stream);
}
-
- return null;
}
protected final List<String> findDocumentPath(File parent, File doc)
@@ -491,7 +498,7 @@
}
protected boolean typeSupportsMetadata(String mimeType) {
- return MIMETYPE_JPG.equals(mimeType) || MIMETYPE_JPEG.equals(mimeType);
+ return MetadataReader.isSupportedMimeType(mimeType);
}
private static String getTypeForName(String name) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index d7ecaec..0700411 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -157,6 +157,15 @@
// Number of transmit power states the Bluetooth controller can be in.
private static final int NUM_BT_TX_LEVELS = 1;
+ /**
+ * Holding a wakelock costs more than just using the cpu.
+ * Currently, we assign only half the cpu time to an app that is running but
+ * not holding a wakelock. The apps holding wakelocks get the rest of the blame.
+ * If no app is holding a wakelock, then the distribution is normal.
+ */
+ @VisibleForTesting
+ public static final int WAKE_LOCK_WEIGHT = 50;
+
protected Clocks mClocks;
private final JournaledFile mFile;
@@ -171,9 +180,12 @@
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
- private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
- private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
- private final KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
+ @VisibleForTesting
+ protected KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
+ @VisibleForTesting
+ protected KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+ @VisibleForTesting
+ protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
new KernelUidCpuFreqTimeReader();
private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
@@ -197,10 +209,12 @@
public static abstract class UserInfoProvider {
private int[] userIds;
protected abstract @Nullable int[] getUserIds();
- private final void refreshUserIds() {
+ @VisibleForTesting
+ public final void refreshUserIds() {
userIds = getUserIds();
}
- private final boolean exists(int userId) {
+ @VisibleForTesting
+ public boolean exists(int userId) {
return userIds != null ? ArrayUtils.contains(userIds, userId) : true;
}
}
@@ -273,7 +287,8 @@
public final MyHandler mHandler;
private ExternalStatsSync mExternalSync = null;
- private UserInfoProvider mUserInfoProvider = null;
+ @VisibleForTesting
+ protected UserInfoProvider mUserInfoProvider = null;
private BatteryCallback mCallback;
@@ -291,7 +306,8 @@
// elapsed time by the number of active timers to arrive at that timer's share of the time.
// In order to do this, we must refresh each timer whenever the number of active timers
// changes.
- final ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
+ @VisibleForTesting
+ protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
@@ -308,7 +324,8 @@
final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
// Last partial timers we use for distributing CPU usage.
- final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
+ @VisibleForTesting
+ protected ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<>();
// These are the objects that will want to do something when the device
// is unplugged from power.
@@ -536,7 +553,8 @@
* in to power.
*/
boolean mOnBattery;
- boolean mOnBatteryInternal;
+ @VisibleForTesting
+ protected boolean mOnBatteryInternal;
/**
* External reporting of whether the device is actually charging.
@@ -606,7 +624,8 @@
private long[] mCpuFreqs;
- private PowerProfile mPowerProfile;
+ @VisibleForTesting
+ protected PowerProfile mPowerProfile;
/*
* Holds a SamplingTimer associated with each kernel wakelock name being tracked.
@@ -2020,7 +2039,8 @@
* For partial wake locks, keep track of whether we are in the list
* to consume CPU cycles.
*/
- boolean mInList;
+ @VisibleForTesting
+ public boolean mInList;
public StopwatchTimer(Clocks clocks, Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
TimeBase timeBase, Parcel in) {
@@ -3632,7 +3652,12 @@
}
public void noteUidProcessStateLocked(int uid, int state) {
- uid = mapUid(uid);
+ int parentUid = mapUid(uid);
+ if (uid != parentUid) {
+ // Isolated UIDs process state is already rolled up into parent, so no need to track
+ // Otherwise the parent's process state will get downgraded incorrectly
+ return;
+ }
getUidStatsLocked(uid).updateUidProcessStateLocked(state);
}
@@ -10262,169 +10287,83 @@
Slog.d(TAG, "!Cpu updating!");
}
- // Holding a wakelock costs more than just using the cpu.
- // Currently, we assign only half the cpu time to an app that is running but
- // not holding a wakelock. The apps holding wakelocks get the rest of the blame.
- // If no app is holding a wakelock, then the distribution is normal.
- final int wakelockWeight = 50;
-
- int numWakelocks = 0;
-
- // Calculate how many wakelocks we have to distribute amongst. The system is excluded.
- // Only distribute cpu power to wakelocks if the screen is off and we're on battery.
- final int numPartialTimers = mPartialTimers.size();
+ // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is
+ // usually holding the wakelock on behalf of an app.
+ // And Only distribute cpu power to wakelocks if the screen is off and we're on battery.
+ ArrayList<StopwatchTimer> partialTimersToConsider = null;
if (mOnBatteryScreenOffTimeBase.isRunning()) {
- for (int i = 0; i < numPartialTimers; i++) {
+ partialTimersToConsider = new ArrayList<>();
+ for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
final StopwatchTimer timer = mPartialTimers.get(i);
+ // Since the collection and blaming of wakelocks can be scheduled to run after
+ // some delay, the mPartialTimers list may have new entries. We can't blame
+ // the newly added timer for past cpu time, so we only consider timers that
+ // were present for one round of collection. Once a timer has gone through
+ // a round of collection, its mInList field is set to true.
if (timer.mInList && timer.mUid != null && timer.mUid.mUid != Process.SYSTEM_UID) {
- // Since the collection and blaming of wakelocks can be scheduled to run after
- // some delay, the mPartialTimers list may have new entries. We can't blame
- // the newly added timer for past cpu time, so we only consider timers that
- // were present for one round of collection. Once a timer has gone through
- // a round of collection, its mInList field is set to true.
- numWakelocks++;
+ partialTimersToConsider.add(timer);
}
}
}
+ markPartialTimersAsEligible();
- final int numWakelocksF = numWakelocks;
- mTempTotalCpuUserTimeUs = 0;
- mTempTotalCpuSystemTimeUs = 0;
+ // When the battery is not on, we don't attribute the cpu times to any timers but we still
+ // need to take the snapshots.
+ if (!mOnBatteryInternal) {
+ mKernelUidCpuTimeReader.readDelta(null);
+ mKernelUidCpuFreqTimeReader.readDelta(null);
+ for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) {
+ mKernelCpuSpeedReaders[cluster].readDelta();
+ }
+ return;
+ }
- final SparseLongArray updatedUids = new SparseLongArray();
-
- // Read the CPU data for each UID. This will internally generate a snapshot so next time
- // we read, we get a delta. If we are to distribute the cpu time, then do so. Otherwise
- // we just ignore the data.
- final long startTimeMs = mClocks.uptimeMillis();
mUserInfoProvider.refreshUserIds();
- mKernelUidCpuTimeReader.readDelta(!mOnBatteryInternal ? null :
- new KernelUidCpuTimeReader.Callback() {
- @Override
- public void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs) {
- uid = mapUid(uid);
- if (Process.isIsolated(uid)) {
- // This could happen if the isolated uid mapping was removed before
- // that process was actually killed.
- mKernelUidCpuTimeReader.removeUid(uid);
- Slog.d(TAG, "Got readings for an isolated uid with"
- + " no mapping to owning uid: " + uid);
- return;
- }
- if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
- Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
- mKernelUidCpuTimeReader.removeUid(uid);
- return;
- }
- final Uid u = getUidStatsLocked(uid);
-
- // Accumulate the total system and user time.
- mTempTotalCpuUserTimeUs += userTimeUs;
- mTempTotalCpuSystemTimeUs += systemTimeUs;
-
- StringBuilder sb = null;
- if (DEBUG_ENERGY_CPU) {
- sb = new StringBuilder();
- sb.append(" got time for uid=").append(u.mUid).append(": u=");
- TimeUtils.formatDuration(userTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeUs / 1000, sb);
- sb.append("\n");
- }
-
- if (numWakelocksF > 0) {
- // We have wakelocks being held, so only give a portion of the
- // time to the process. The rest will be distributed among wakelock
- // holders.
- userTimeUs = (userTimeUs * wakelockWeight) / 100;
- systemTimeUs = (systemTimeUs * wakelockWeight) / 100;
- }
-
- if (sb != null) {
- sb.append(" adding to uid=").append(u.mUid).append(": u=");
- TimeUtils.formatDuration(userTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeUs / 1000, sb);
- Slog.d(TAG, sb.toString());
- }
-
- u.mUserCpuTime.addCountLocked(userTimeUs);
- u.mSystemCpuTime.addCountLocked(systemTimeUs);
- updatedUids.put(u.getUid(), userTimeUs + systemTimeUs);
- }
- });
-
+ final SparseLongArray updatedUids = new SparseLongArray();
+ readKernelUidCpuTimesLocked(partialTimersToConsider, updatedUids);
+ updateClusterSpeedTimes(updatedUids);
if (updateCpuFreqData) {
readKernelUidCpuFreqTimesLocked();
}
+ }
- final long elapse = (mClocks.uptimeMillis() - startTimeMs);
- if (DEBUG_ENERGY_CPU || (elapse >= 100)) {
- Slog.d(TAG, "Reading cpu stats took " + elapse + " ms");
- }
+ /**
+ * Mark the current partial timers as gone through a collection so that they will be
+ * considered in the next cpu times distribution to wakelock holders.
+ */
+ @VisibleForTesting
+ public void markPartialTimersAsEligible() {
+ if (ArrayUtils.referenceEquals(mPartialTimers, mLastPartialTimers)) {
+ // No difference, so each timer is now considered for the next collection.
+ for (int i = mPartialTimers.size() - 1; i >= 0; --i) {
+ mPartialTimers.get(i).mInList = true;
+ }
+ } else {
+ // The lists are different, meaning we added (or removed a timer) since the last
+ // collection.
+ for (int i = mLastPartialTimers.size() - 1; i >= 0; --i) {
+ mLastPartialTimers.get(i).mInList = false;
+ }
+ mLastPartialTimers.clear();
- if (mOnBatteryInternal && numWakelocks > 0) {
- // Distribute a portion of the total cpu time to wakelock holders.
- mTempTotalCpuUserTimeUs = (mTempTotalCpuUserTimeUs * (100 - wakelockWeight)) / 100;
- mTempTotalCpuSystemTimeUs =
- (mTempTotalCpuSystemTimeUs * (100 - wakelockWeight)) / 100;
-
- for (int i = 0; i < numPartialTimers; i++) {
+ // Mark the current timers as gone through a collection.
+ final int numPartialTimers = mPartialTimers.size();
+ for (int i = 0; i < numPartialTimers; ++i) {
final StopwatchTimer timer = mPartialTimers.get(i);
-
- // The system does not share any blame, as it is usually holding the wakelock
- // on behalf of an app.
- if (timer.mInList && timer.mUid != null && timer.mUid.mUid != Process.SYSTEM_UID) {
- int userTimeUs = (int) (mTempTotalCpuUserTimeUs / numWakelocks);
- int systemTimeUs = (int) (mTempTotalCpuSystemTimeUs / numWakelocks);
-
- if (DEBUG_ENERGY_CPU) {
- StringBuilder sb = new StringBuilder();
- sb.append(" Distributing wakelock uid=").append(timer.mUid.mUid)
- .append(": u=");
- TimeUtils.formatDuration(userTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(systemTimeUs / 1000, sb);
- Slog.d(TAG, sb.toString());
- }
-
- timer.mUid.mUserCpuTime.addCountLocked(userTimeUs);
- timer.mUid.mSystemCpuTime.addCountLocked(systemTimeUs);
- final int uid = timer.mUid.getUid();
- updatedUids.put(uid, updatedUids.get(uid, 0) + userTimeUs + systemTimeUs);
-
- final Uid.Proc proc = timer.mUid.getProcessStatsLocked("*wakelock*");
- proc.addCpuTimeLocked(userTimeUs / 1000, systemTimeUs / 1000);
-
- mTempTotalCpuUserTimeUs -= userTimeUs;
- mTempTotalCpuSystemTimeUs -= systemTimeUs;
- numWakelocks--;
- }
- }
-
- if (mTempTotalCpuUserTimeUs > 0 || mTempTotalCpuSystemTimeUs > 0) {
- // Anything left over is given to the system.
- if (DEBUG_ENERGY_CPU) {
- StringBuilder sb = new StringBuilder();
- sb.append(" Distributing lost time to system: u=");
- TimeUtils.formatDuration(mTempTotalCpuUserTimeUs / 1000, sb);
- sb.append(" s=");
- TimeUtils.formatDuration(mTempTotalCpuSystemTimeUs / 1000, sb);
- Slog.d(TAG, sb.toString());
- }
-
- final Uid u = getUidStatsLocked(Process.SYSTEM_UID);
- u.mUserCpuTime.addCountLocked(mTempTotalCpuUserTimeUs);
- u.mSystemCpuTime.addCountLocked(mTempTotalCpuSystemTimeUs);
- updatedUids.put(Process.SYSTEM_UID, updatedUids.get(Process.SYSTEM_UID, 0)
- + mTempTotalCpuUserTimeUs + mTempTotalCpuSystemTimeUs);
-
- final Uid.Proc proc = u.getProcessStatsLocked("*lost*");
- proc.addCpuTimeLocked((int) mTempTotalCpuUserTimeUs / 1000,
- (int) mTempTotalCpuSystemTimeUs / 1000);
+ timer.mInList = true;
+ mLastPartialTimers.add(timer);
}
}
+ }
+ /**
+ * Take snapshot of cpu times (aggregated over all uids) at different frequencies and
+ * calculate cpu times spent by each uid at different frequencies.
+ *
+ * @param updatedUids The uids for which times spent at different frequencies are calculated.
+ */
+ @VisibleForTesting
+ public void updateClusterSpeedTimes(@NonNull SparseLongArray updatedUids) {
long totalCpuClustersTimeMs = 0;
// Read the time spent for each cluster at various cpu frequencies.
final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][];
@@ -10446,8 +10385,8 @@
final long appCpuTimeUs = updatedUids.valueAt(i);
// Add the cpu speeds to this UID.
final int numClusters = mPowerProfile.getNumCpuClusters();
- if (u.mCpuClusterSpeedTimesUs == null || u.mCpuClusterSpeedTimesUs.length !=
- numClusters) {
+ if (u.mCpuClusterSpeedTimesUs == null ||
+ u.mCpuClusterSpeedTimesUs.length != numClusters) {
u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][];
}
@@ -10471,66 +10410,155 @@
}
}
}
+ }
- // See if there is a difference in wakelocks between this collection and the last
- // collection.
- if (ArrayUtils.referenceEquals(mPartialTimers, mLastPartialTimers)) {
- // No difference, so each timer is now considered for the next collection.
- for (int i = 0; i < numPartialTimers; i++) {
- mPartialTimers.get(i).mInList = true;
- }
- } else {
- // The lists are different, meaning we added (or removed a timer) since the last
- // collection.
- final int numLastPartialTimers = mLastPartialTimers.size();
- for (int i = 0; i < numLastPartialTimers; i++) {
- mLastPartialTimers.get(i).mInList = false;
- }
- mLastPartialTimers.clear();
+ /**
+ * Take a snapshot of the cpu times spent by each uid and update the corresponding counters.
+ * If {@param partialTimers} is not null and empty, then we assign a portion of cpu times to
+ * wakelock holders.
+ *
+ * @param partialTimers The wakelock holders among which the cpu times will be distributed.
+ * @param updatedUids If not null, then the uids found in the snapshot will be added to this.
+ */
+ @VisibleForTesting
+ public void readKernelUidCpuTimesLocked(@Nullable ArrayList<StopwatchTimer> partialTimers,
+ @Nullable SparseLongArray updatedUids) {
+ mTempTotalCpuUserTimeUs = mTempTotalCpuSystemTimeUs = 0;
+ final int numWakelocks = partialTimers == null ? 0 : partialTimers.size();
+ final long startTimeMs = mClocks.uptimeMillis();
- // Mark the current timers as gone through a collection.
- for (int i = 0; i < numPartialTimers; i++) {
- final StopwatchTimer timer = mPartialTimers.get(i);
- timer.mInList = true;
- mLastPartialTimers.add(timer);
+ mKernelUidCpuTimeReader.readDelta((uid, userTimeUs, systemTimeUs) -> {
+ uid = mapUid(uid);
+ if (Process.isIsolated(uid)) {
+ // This could happen if the isolated uid mapping was removed before that process
+ // was actually killed.
+ mKernelUidCpuTimeReader.removeUid(uid);
+ Slog.d(TAG, "Got readings for an isolated uid with no mapping: " + uid);
+ return;
+ }
+ if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+ Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
+ mKernelUidCpuTimeReader.removeUid(uid);
+ return;
+ }
+ final Uid u = getUidStatsLocked(uid);
+
+ // Accumulate the total system and user time.
+ mTempTotalCpuUserTimeUs += userTimeUs;
+ mTempTotalCpuSystemTimeUs += systemTimeUs;
+
+ StringBuilder sb = null;
+ if (DEBUG_ENERGY_CPU) {
+ sb = new StringBuilder();
+ sb.append(" got time for uid=").append(u.mUid).append(": u=");
+ TimeUtils.formatDuration(userTimeUs / 1000, sb);
+ sb.append(" s=");
+ TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+ sb.append("\n");
+ }
+
+ if (numWakelocks > 0) {
+ // We have wakelocks being held, so only give a portion of the
+ // time to the process. The rest will be distributed among wakelock
+ // holders.
+ userTimeUs = (userTimeUs * WAKE_LOCK_WEIGHT) / 100;
+ systemTimeUs = (systemTimeUs * WAKE_LOCK_WEIGHT) / 100;
+ }
+
+ if (sb != null) {
+ sb.append(" adding to uid=").append(u.mUid).append(": u=");
+ TimeUtils.formatDuration(userTimeUs / 1000, sb);
+ sb.append(" s=");
+ TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+ Slog.d(TAG, sb.toString());
+ }
+
+ u.mUserCpuTime.addCountLocked(userTimeUs);
+ u.mSystemCpuTime.addCountLocked(systemTimeUs);
+ if (updatedUids != null) {
+ updatedUids.put(u.getUid(), userTimeUs + systemTimeUs);
+ }
+ });
+
+ final long elapsedTimeMs = mClocks.uptimeMillis() - startTimeMs;
+ if (DEBUG_ENERGY_CPU || elapsedTimeMs >= 100) {
+ Slog.d(TAG, "Reading cpu stats took " + elapsedTimeMs + "ms");
+ }
+
+ if (numWakelocks > 0) {
+ // Distribute a portion of the total cpu time to wakelock holders.
+ mTempTotalCpuUserTimeUs = (mTempTotalCpuUserTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+ mTempTotalCpuSystemTimeUs =
+ (mTempTotalCpuSystemTimeUs * (100 - WAKE_LOCK_WEIGHT)) / 100;
+
+ for (int i = 0; i < numWakelocks; ++i) {
+ final StopwatchTimer timer = partialTimers.get(i);
+ final int userTimeUs = (int) (mTempTotalCpuUserTimeUs / (numWakelocks - i));
+ final int systemTimeUs = (int) (mTempTotalCpuSystemTimeUs / (numWakelocks - i));
+
+ if (DEBUG_ENERGY_CPU) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(" Distributing wakelock uid=").append(timer.mUid.mUid)
+ .append(": u=");
+ TimeUtils.formatDuration(userTimeUs / 1000, sb);
+ sb.append(" s=");
+ TimeUtils.formatDuration(systemTimeUs / 1000, sb);
+ Slog.d(TAG, sb.toString());
+ }
+
+ timer.mUid.mUserCpuTime.addCountLocked(userTimeUs);
+ timer.mUid.mSystemCpuTime.addCountLocked(systemTimeUs);
+ if (updatedUids != null) {
+ final int uid = timer.mUid.getUid();
+ updatedUids.put(uid, updatedUids.get(uid, 0) + userTimeUs + systemTimeUs);
+ }
+
+ final Uid.Proc proc = timer.mUid.getProcessStatsLocked("*wakelock*");
+ proc.addCpuTimeLocked(userTimeUs / 1000, systemTimeUs / 1000);
+
+ mTempTotalCpuUserTimeUs -= userTimeUs;
+ mTempTotalCpuSystemTimeUs -= systemTimeUs;
}
}
}
- void readKernelUidCpuFreqTimesLocked() {
- mKernelUidCpuFreqTimeReader.readDelta(!mOnBatteryInternal ? null :
- new KernelUidCpuFreqTimeReader.Callback() {
- @Override
- public void onCpuFreqs(long[] cpuFreqs) {
- mCpuFreqs = cpuFreqs;
- }
+ /**
+ * Take a snapshot of the cpu times spent by each uid in each freq and update the
+ * corresponding counters.
+ */
+ @VisibleForTesting
+ public void readKernelUidCpuFreqTimesLocked() {
+ mKernelUidCpuFreqTimeReader.readDelta(new KernelUidCpuFreqTimeReader.Callback() {
+ @Override
+ public void onCpuFreqs(long[] cpuFreqs) {
+ mCpuFreqs = cpuFreqs;
+ }
- @Override
- public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
- uid = mapUid(uid);
- if (Process.isIsolated(uid)) {
- mKernelUidCpuFreqTimeReader.removeUid(uid);
- Slog.d(TAG, "Got freq readings for an isolated uid with"
- + " no mapping to owning uid: " + uid);
- return;
- }
- if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
- Slog.d(TAG, "Got readings for an invalid user's uid " + uid);
- mKernelUidCpuFreqTimeReader.removeUid(uid);
- return;
- }
- final Uid u = getUidStatsLocked(uid);
- if (u.mCpuFreqTimeMs == null) {
- u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
- }
- u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
- if (u.mScreenOffCpuFreqTimeMs == null) {
- u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
- mOnBatteryScreenOffTimeBase);
- }
- u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
- }
- });
+ @Override
+ public void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs) {
+ uid = mapUid(uid);
+ if (Process.isIsolated(uid)) {
+ mKernelUidCpuFreqTimeReader.removeUid(uid);
+ Slog.d(TAG, "Got freq readings for an isolated uid with no mapping: " + uid);
+ return;
+ }
+ if (!mUserInfoProvider.exists(UserHandle.getUserId(uid))) {
+ Slog.d(TAG, "Got freq readings for an invalid user's uid " + uid);
+ mKernelUidCpuFreqTimeReader.removeUid(uid);
+ return;
+ }
+ final Uid u = getUidStatsLocked(uid);
+ if (u.mCpuFreqTimeMs == null) {
+ u.mCpuFreqTimeMs = new LongSamplingCounterArray(mOnBatteryTimeBase);
+ }
+ u.mCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+ if (u.mScreenOffCpuFreqTimeMs == null) {
+ u.mScreenOffCpuFreqTimeMs = new LongSamplingCounterArray(
+ mOnBatteryScreenOffTimeBase);
+ }
+ u.mScreenOffCpuFreqTimeMs.addCountLocked(cpuFreqTimeMs);
+ }
+ });
}
boolean setChargingLocked(boolean charging) {
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 5375651..a96b5dd 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -35,7 +35,8 @@
import java.io.IOException;
/**
- * Backup agent for various system-managed data, currently just the system wallpaper
+ * Backup agent for various system-managed data. Wallpapers are now handled by a
+ * separate package, but we still process restores from legacy datasets here.
*/
public class SystemBackupAgent extends BackupAgentHelper {
private static final String TAG = "SystemBackupAgent";
@@ -61,16 +62,19 @@
// TODO: http://b/22388012
private static final String WALLPAPER_IMAGE_DIR =
Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM).getAbsolutePath();
- private static final String WALLPAPER_IMAGE = WallpaperBackupHelper.WALLPAPER_IMAGE;
+ public static final String WALLPAPER_IMAGE =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
+ "wallpaper").getAbsolutePath();
// TODO: Will need to change if backing up non-primary user's wallpaper
// TODO: http://b/22388012
private static final String WALLPAPER_INFO_DIR =
Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM).getAbsolutePath();
- private static final String WALLPAPER_INFO = WallpaperBackupHelper.WALLPAPER_INFO;
+ public static final String WALLPAPER_INFO =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM),
+ "wallpaper_info.xml").getAbsolutePath();
// Use old keys to keep legacy data compatibility and avoid writing two wallpapers
private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY;
- private static final String WALLPAPER_INFO_KEY = WallpaperBackupHelper.WALLPAPER_INFO_KEY;
private WallpaperBackupHelper mWallpaperHelper = null;
@@ -98,13 +102,11 @@
// Slot in a restore helper for the older wallpaper backup schema to support restore
// from devices still generating data in that format.
mWallpaperHelper = new WallpaperBackupHelper(this,
- new String[] { WALLPAPER_IMAGE, WALLPAPER_INFO },
- new String[] { WALLPAPER_IMAGE_KEY, WALLPAPER_INFO_KEY} );
+ new String[] { WALLPAPER_IMAGE_KEY} );
addHelper(WALLPAPER_HELPER, mWallpaperHelper);
// On restore, we also support a long-ago wallpaper data schema "system_files"
addHelper("system_files", new WallpaperBackupHelper(this,
- new String[] { WALLPAPER_IMAGE },
new String[] { WALLPAPER_IMAGE_KEY} ));
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
@@ -115,27 +117,12 @@
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
- try {
- super.onRestore(data, appVersionCode, newState);
-
- IWallpaperManager wallpaper = (IWallpaperManager) ServiceManager.getService(
- Context.WALLPAPER_SERVICE);
- if (wallpaper != null) {
- try {
- wallpaper.settingsRestored();
- } catch (RemoteException re) {
- Slog.e(TAG, "Couldn't restore settings\n" + re);
- }
- }
- } catch (IOException ex) {
- // If there was a failure, delete everything for the wallpaper, this is too aggressive,
- // but this is hopefully a rare failure.
- Slog.d(TAG, "restore failed", ex);
- (new File(WALLPAPER_IMAGE)).delete();
- (new File(WALLPAPER_INFO)).delete();
- }
+ super.onRestore(data, appVersionCode, newState);
}
+ /**
+ * Support for 'adb restore' of legacy archives
+ */
@Override
public void onRestoreFile(ParcelFileDescriptor data, long size,
int type, String domain, String path, long mode, long mtime)
@@ -183,12 +170,4 @@
}
}
}
-
- @Override
- public void onRestoreFinished() {
- // helper will be null following 'adb restore' or other full-data operation
- if (mWallpaperHelper != null) {
- mWallpaperHelper.onRestoreFinished();
- }
- }
}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1ad8541..5afd067 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -814,6 +814,12 @@
addOption("-Ximage-compiler-option");
addOption("--compiled-classes=/system/etc/compiled-classes");
}
+
+ // If there is a dirty-image-objects file, push it.
+ if (hasFile("/system/etc/dirty-image-objects")) {
+ addOption("-Ximage-compiler-option");
+ addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+ }
}
property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 7b381b4..bfb25113 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -44,7 +44,7 @@
mWidth = width;
mHeight = height;
SkCanvas* canvas = mRecorder->beginRecording(SkIntToScalar(width), SkIntToScalar(height));
- return Canvas::create_canvas(canvas);
+ return Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer);
}
void Picture::endRecording() {
diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp
index e2dc52b..abc3599 100644
--- a/core/jni/android/graphics/pdf/PdfDocument.cpp
+++ b/core/jni/android/graphics/pdf/PdfDocument.cpp
@@ -21,6 +21,7 @@
#include "CreateJavaOutputStreamAdaptor.h"
+#include "SkColorSpaceXformCanvas.h"
#include "SkDocument.h"
#include "SkPicture.h"
#include "SkPictureRecorder.h"
@@ -94,7 +95,10 @@
SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight,
&(page->mContentRect));
- canvas->drawPicture(page->mPicture);
+ std::unique_ptr<SkCanvas> toSRGBCanvas =
+ SkCreateColorSpaceXformCanvas(canvas, SkColorSpace::MakeSRGB());
+
+ toSRGBCanvas->drawPicture(page->mPicture);
document->endPage();
}
@@ -127,7 +131,7 @@
PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr);
SkCanvas* canvas = document->startPage(pageWidth, pageHeight,
contentLeft, contentTop, contentRight, contentBottom);
- return reinterpret_cast<jlong>(Canvas::create_canvas(canvas));
+ return reinterpret_cast<jlong>(Canvas::create_canvas(canvas, Canvas::XformToSRGB::kDefer));
}
static void nativeFinishPage(JNIEnv* env, jobject thiz, jlong documentPtr) {
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 31e954b..c457ab0 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -233,8 +233,9 @@
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
+ int32_t displayId;
status_t status = mInputConsumer.consume(&mInputEventFactory,
- consumeBatches, frameTime, &seq, &inputEvent);
+ consumeBatches, frameTime, &seq, &inputEvent, &displayId);
if (status) {
if (status == WOULD_BLOCK) {
if (!skipCallbacks && !mBatchedInputEventPending
@@ -311,7 +312,8 @@
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
}
env->CallVoidMethod(receiverObj.get(),
- gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
+ gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
+ displayId);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
@@ -417,7 +419,7 @@
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
- "dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+ "dispatchInputEvent", "(ILandroid/view/InputEvent;I)V");
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 420ff2a..58ccef18 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -39,6 +39,8 @@
// Log debug messages about the dispatch cycle.
static const bool kDebugDispatchCycle = false;
+// Display id for default(primary) display.
+static const int32_t kDefaultDisplayId = 0;
static struct {
jclass clazz;
@@ -136,6 +138,7 @@
publishedSeq = mNextPublishedSeq++;
status_t status = mInputPublisher.publishMotionEvent(publishedSeq,
event->getDeviceId(), event->getSource(),
+ kDefaultDisplayId /* TODO(multi-display): propagate display id */,
event->getAction(), event->getActionButton(), event->getFlags(),
event->getEdgeFlags(), event->getMetaState(), event->getButtonState(),
event->getXOffset(), event->getYOffset(),
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3887c76..dba8194 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -108,13 +108,9 @@
// changes its locking strategy or its use of syscalls within the
// lazy-init critical section, its use here may become unsafe.
if (WIFEXITED(status)) {
- if (WEXITSTATUS(status)) {
- ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
- }
+ ALOGI("Process %d exited cleanly (%d)", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
- if (WTERMSIG(status) != SIGKILL) {
- ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
- }
+ ALOGI("Process %d exited due to signal (%d)", pid, WTERMSIG(status));
if (WCOREDUMP(status)) {
ALOGI("Process %d dumped core.", pid);
}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 5914f56..76e683f 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -31,6 +31,7 @@
import "frameworks/base/core/proto/android/service/print.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
+import "frameworks/base/core/proto/android/os/procrank.proto";
package android.os;
@@ -53,7 +54,7 @@
//SystemProperties system_properties = 1000;
// Linux services
- //Procrank procrank = 2000;
+ Procrank procrank = 2000;
//PageTypeInfo page_type_info = 2001;
KernelWakeSources kernel_wake_sources = 2002;
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 6ab1d29..e0b62aa 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -26,6 +26,7 @@
repeated WakeupSourceProto wakeup_sources = 1;
}
+// Next Tag: 11
message WakeupSourceProto {
// Name of the event which triggers application processor
string name = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
new file mode 100644
index 0000000..c7dbf4d
--- /dev/null
+++ b/core/proto/android/os/procrank.proto
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 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 = "proto3";
+
+option java_multiple_files = true;
+option java_outer_classname = "ProcrankProto";
+
+package android.os;
+
+//Memory usage of running processes
+message Procrank {
+ // Currently running process
+ repeated ProcessProto processes = 1;
+
+ // Summary
+ SummaryProto summary = 2;
+}
+
+// Next Tag: 11
+message ProcessProto {
+ // ID of the process
+ int32 pid = 1;
+
+ // virtual set size, unit KB
+ int64 vss = 2;
+
+ // resident set size, unit KB
+ int64 rss = 3;
+
+ // proportional set size, unit KB
+ int64 pss = 4;
+
+ // unique set size, unit KB
+ int64 uss = 5;
+
+ // swap size, unit KB
+ int64 swap = 6;
+
+ // proportional swap size, unit KB
+ int64 pswap = 7;
+
+ // unique swap size, unit KB
+ int64 uswap = 8;
+
+ // zswap size, unit KB
+ int64 zswap = 9;
+
+ // process command
+ string cmdline = 10;
+}
+
+// Next Tag: 3
+message SummaryProto {
+ ProcessProto total = 1;
+
+ ZramProto zram = 2;
+
+ RamProto ram = 3;
+}
+
+// TODO: sync on how to use these values
+message ZramProto {
+ string raw_text = 1;
+}
+
+message RamProto {
+ string raw_text = 1;
+}
diff --git a/core/res/res/drawable/toast_frame.xml b/core/res/res/drawable/toast_frame.xml
index 053b4f4..d57bd6a 100644
--- a/core/res/res/drawable/toast_frame.xml
+++ b/core/res/res/drawable/toast_frame.xml
@@ -17,8 +17,8 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
- <!-- background is material_grey_300 with .9 alpha -->
- <solid android:color="#E6E0E0E0" />
+ <!-- background is material_grey_200 with .9 alpha -->
+ <solid android:color="#E6EEEEEE" />
<corners android:radius="22dp" />
</shape>
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 2c08bf7..db586ec 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -34,8 +34,6 @@
android:layout_gravity="center_horizontal"
android:textAppearance="@style/TextAppearance.Toast"
android:textColor="@color/primary_text_default_material_light"
- android:shadowColor="#BB000000"
- android:shadowRadius="2.75"
/>
</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4038010..4b64e3f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -525,6 +525,9 @@
<!-- Integers specifying the max packet Tx/Rx rates for full scan -->
<integer translatable="false" name="config_wifi_framework_max_tx_rate_for_full_scan">8</integer>
<integer translatable="false" name="config_wifi_framework_max_rx_rate_for_full_scan">16</integer>
+ <!-- Integers specifying the min packet Tx/Rx rates in packets per second for staying on the same network -->
+ <integer translatable="false" name="config_wifi_framework_min_tx_rate_for_staying_on_network">16</integer>
+ <integer translatable="false" name="config_wifi_framework_min_rx_rate_for_staying_on_network">16</integer>
<!-- Integer parameters of the wifi to cellular handover feature
wifi should not stick to bad networks -->
<integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-82</integer>
@@ -1388,6 +1391,7 @@
<!-- The package name of the default network recommendation app.
A network recommendation provider must:
* Be granted the SCORE_NETWORKS permission.
+ * Be granted the ACCESS_COARSE_LOCATION permission.
* Include a Service for the android.net.scoring.RECOMMEND_NETWORKS action
protected by the BIND_NETWORK_RECOMMENDATION_SERVICE permission.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index efd199e..d5a28f1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -377,6 +377,8 @@
<java-symbol type="string" name="config_wifi_framework_sap_2G_channel_list" />
<java-symbol type="integer" name="config_wifi_framework_max_tx_rate_for_full_scan" />
<java-symbol type="integer" name="config_wifi_framework_max_rx_rate_for_full_scan" />
+ <java-symbol type="integer" name="config_wifi_framework_min_tx_rate_for_staying_on_network" />
+ <java-symbol type="integer" name="config_wifi_framework_min_rx_rate_for_staying_on_network" />
<java-symbol type="bool" name="config_wifi_framework_cellular_handover_enable_user_triggered_adjustment" />
<java-symbol type="integer" name="config_wifi_framework_associated_full_scan_tx_packet_threshold" />
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 6063e1a..0920d1e 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -33,6 +33,7 @@
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
+import java.util.TimeZone;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -40,16 +41,20 @@
private static final LocaleList LOCALE_LIST_US = new LocaleList(Locale.US);
private LocaleList mOriginalLocales;
+ private TimeZone mOriginalTimeZone;
@Before
public void setup() {
mOriginalLocales = Resources.getSystem().getConfiguration().getLocales();
setLocales(LOCALE_LIST_US);
+ mOriginalTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
}
@After
public void teardown() {
setLocales(mOriginalLocales);
+ TimeZone.setDefault(mOriginalTimeZone);
}
@Test
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index 29447ed..2a6c22e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -32,9 +32,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-import java.util.Locale;
-
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TextClassificationManagerTest {
@@ -177,20 +174,6 @@
}
@Test
- public void testLanguageDetection() {
- if (isTextClassifierDisabled()) return;
-
- String text = "This is a piece of English text";
- assertThat(mTcm.detectLanguages(text), isDetectedLanguage("en"));
-
- text = "Das ist ein deutscher Text";
- assertThat(mTcm.detectLanguages(text), isDetectedLanguage("de"));
-
- text = "これは日本語のテキストです";
- assertThat(mTcm.detectLanguages(text), isDetectedLanguage("ja"));
- }
-
- @Test
public void testSetTextClassifier() {
TextClassifier classifier = mock(TextClassifier.class);
mTcm.setTextClassifier(classifier);
@@ -270,30 +253,4 @@
}
};
}
-
- private static Matcher<List<TextLanguage>> isDetectedLanguage(final String language) {
- return new BaseMatcher<List<TextLanguage>>() {
- @Override
- public boolean matches(Object o) {
- if (o instanceof List) {
- List languages = (List) o;
- if (!languages.isEmpty()) {
- Object o1 = languages.get(0);
- if (o1 instanceof TextLanguage) {
- TextLanguage lang = (TextLanguage) o1;
- return lang.getLanguageCount() > 0
- && new Locale(language).getLanguage()
- .equals(lang.getLanguage(0).getLanguage());
- }
- }
- }
- return false;
- }
-
- @Override
- public void describeTo(Description description) {
- description.appendValue(String.format("%s", language));
- }
- };
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
new file mode 100644
index 0000000..e69cd5d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -0,0 +1,774 @@
+/*
+ * 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.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.Process.FIRST_ISOLATED_UID;
+
+import static com.android.internal.os.BatteryStatsImpl.WAKE_LOCK_WEIGHT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.os.Process;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseLongArray;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * To run the tests, use
+ *
+ * runtest -c com.android.internal.os.BatteryStatsCpuTimesTest frameworks-core
+ *
+ * or
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class com.android.internal.os.BatteryStatsCpuTimesTest -w \
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ *
+ * or
+ *
+ * bit FrameworksCoreTests:com.android.internal.os.BatteryStatsCpuTimesTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsCpuTimesTest {
+ @Mock KernelUidCpuTimeReader mKernelUidCpuTimeReader;
+ @Mock KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+ @Mock BatteryStatsImpl.UserInfoProvider mUserInfoProvider;
+ @Mock PowerProfile mPowerProfile;
+
+ private MockClocks mClocks;
+ private MockBatteryStatsImpl mBatteryStatsImpl;
+ private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mClocks = new MockClocks();
+ mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks)
+ .setKernelUidCpuTimeReader(mKernelUidCpuTimeReader)
+ .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+ .setUserInfoProvider(mUserInfoProvider);
+ }
+
+ @Test
+ public void testUpdateCpuTimeLocked() {
+ // PRECONDITIONS
+ mBatteryStatsImpl.setPowerProfile(mPowerProfile);
+ mBatteryStatsImpl.setOnBatteryInternal(false);
+ final int numClusters = 3;
+ initKernelCpuSpeedReaders(numClusters);
+
+ // RUN
+ mBatteryStatsImpl.updateCpuTimeLocked(true);
+
+ // VERIFY
+ verify(mKernelUidCpuTimeReader).readDelta(null);
+ verify(mKernelUidCpuFreqTimeReader).readDelta(null);
+ for (int i = 0; i < numClusters; ++i) {
+ verify(mKernelCpuSpeedReaders[i]).readDelta();
+ }
+
+ // Prepare for next test
+ Mockito.reset(mUserInfoProvider, mKernelUidCpuFreqTimeReader, mKernelUidCpuTimeReader);
+ for (int i = 0; i < numClusters; ++i) {
+ Mockito.reset(mKernelCpuSpeedReaders[i]);
+ }
+
+ // PRECONDITIONS
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+
+ // RUN
+ mBatteryStatsImpl.updateCpuTimeLocked(true);
+
+ // VERIFY
+ verify(mUserInfoProvider).refreshUserIds();
+ verify(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+ verify(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+ for (int i = 0; i < numClusters; ++i) {
+ verify(mKernelCpuSpeedReaders[i]).readDelta();
+ }
+ }
+
+ @Test
+ public void testMarkPartialTimersAsEligible() {
+ // PRECONDITIONS
+ final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers = getPartialTimers(
+ 10032, 10042, 10052);
+ final ArrayList<BatteryStatsImpl.StopwatchTimer> lastPartialTimers
+ = new ArrayList<>(partialTimers);
+ mBatteryStatsImpl.setPartialTimers(partialTimers);
+ mBatteryStatsImpl.setLastPartialTimers(lastPartialTimers);
+ final boolean[] inList = {false, true, false};
+ for (int i = 0; i < partialTimers.size(); ++i) {
+ partialTimers.get(i).mInList = inList[i];
+ }
+
+ // RUN
+ mBatteryStatsImpl.markPartialTimersAsEligible();
+
+ // VERIFY
+ assertTrue(ArrayUtils.referenceEquals(partialTimers, lastPartialTimers));
+ for (int i = 0; i < partialTimers.size(); ++i) {
+ assertTrue("Timer id=" + i, partialTimers.get(i).mInList);
+ }
+
+ // PRECONDITIONS
+ partialTimers.addAll(getPartialTimers(10077, 10099));
+ partialTimers.remove(1 /* index */);
+
+ // RUN
+ mBatteryStatsImpl.markPartialTimersAsEligible();
+
+ // VERIFY
+ assertTrue(ArrayUtils.referenceEquals(partialTimers, lastPartialTimers));
+ for (int i = 0; i < partialTimers.size(); ++i) {
+ assertTrue("Timer id=" + i, partialTimers.get(i).mInList);
+ }
+ }
+
+ @Test
+ public void testUpdateClusterSpeedTimes() {
+ // PRECONDITIONS
+
+ // Call updateTimeBasesLocked before setting the power profile to avoid execution of
+ // BatteryStatsImpl.updateCpuTimeLocked.
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+ mBatteryStatsImpl.setPowerProfile(mPowerProfile);
+ final long[][] clusterSpeedTimesMs = {{20, 30}, {40, 50, 60}};
+ initKernelCpuSpeedReaders(clusterSpeedTimesMs.length);
+ for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+ when(mKernelCpuSpeedReaders[i].readDelta()).thenReturn(clusterSpeedTimesMs[i]);
+ }
+ when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterSpeedTimesMs.length);
+ for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+ when(mPowerProfile.getNumSpeedStepsInCpuCluster(i))
+ .thenReturn(clusterSpeedTimesMs[i].length);
+ }
+ final SparseLongArray updatedUids = new SparseLongArray();
+ final int[] testUids = {10012, 10014, 10016};
+ final int[] cpuTimeUs = {89, 31, 43};
+ for (int i = 0; i < testUids.length; ++i) {
+ updatedUids.put(testUids[i], cpuTimeUs[i]);
+ }
+
+ // RUN
+ mBatteryStatsImpl.updateClusterSpeedTimes(updatedUids);
+
+ // VERIFY
+ int totalClustersTimeMs = 0;
+ for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
+ for (int j = 0; j < clusterSpeedTimesMs[i].length; ++j) {
+ totalClustersTimeMs += clusterSpeedTimesMs[i][j];
+ }
+ }
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ for (int cluster = 0; cluster < clusterSpeedTimesMs.length; ++cluster) {
+ for (int speed = 0; speed < clusterSpeedTimesMs[cluster].length; ++speed) {
+ assertEquals("Uid=" + testUids[i] + ", cluster=" + cluster + ", speed=" + speed,
+ cpuTimeUs[i] * clusterSpeedTimesMs[cluster][speed]
+ / totalClustersTimeMs,
+ u.getTimeAtCpuSpeed(cluster, speed, STATS_SINCE_CHARGED));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testReadKernelUidCpuTimesLocked() {
+ //PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+ final int testUserId = 11;
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ FIRST_APPLICATION_UID + 27,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[][] uidTimesUs = {
+ {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ }
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ final SparseLongArray updatedUids = new SparseLongArray();
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, updatedUids);
+
+ // VERIFY
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+
+ assertEquals("Unexpected entry in updated uids for uid=" + testUids[i],
+ uidTimesUs[i][0] + uidTimesUs[i][1], updatedUids.get(testUids[i]));
+ updatedUids.delete(testUids[i]);
+ }
+ assertEquals("Updated uids: " + updatedUids, 0, updatedUids.size());
+
+ // Repeat the test with a null updatedUids
+
+ // PRECONDITIONS
+ final long[][] deltasUs = {
+ {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ }
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+ // VERIFY
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ uidTimesUs[i][0] + deltasUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ uidTimesUs[i][1] + deltasUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ }
+ }
+
+ @Test
+ public void testReadKernelUidCpuTimesLocked_isolatedUid() {
+ //PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+ final int testUserId = 11;
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ final int isolatedAppId = FIRST_ISOLATED_UID + 27;
+ final int isolatedUid = UserHandle.getUid(testUserId, isolatedAppId);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ isolatedAppId,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[][] uidTimesUs = {
+ {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ }
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+ // VERIFY
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ if (UserHandle.isIsolated(testUids[i])) {
+ assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+ continue;
+ }
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ }
+ verify(mKernelUidCpuTimeReader).removeUid(isolatedUid);
+
+ // Add an isolated uid mapping and repeat the test.
+
+ // PRECONDITIONS
+ final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
+ mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+ final long[][] deltasUs = {
+ {9379, 3332409833484l}, {493247, 723234}, {3247819, 123348}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], deltasUs[i][0], deltasUs[i][1]);
+ }
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+ // VERIFY
+ for (int i = 0; i < testUids.length; ++i) {
+ BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ final long expectedUserTimeUs;
+ final long expectedSystemTimeUs;
+ if (UserHandle.isIsolated(testUids[i])) {
+ assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+ // Since we added a mapping, an entry should've been created for owner uid.
+ u = mBatteryStatsImpl.getUidStats().get(ownerUid);
+ expectedUserTimeUs = deltasUs[i][0];
+ expectedSystemTimeUs = deltasUs[i][1];
+ assertNotNull("No entry for owner uid=" + ownerUid, u);
+ } else {
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ expectedUserTimeUs = uidTimesUs[i][0] + deltasUs[i][0];
+ expectedSystemTimeUs = uidTimesUs[i][1] + deltasUs[i][1];
+ }
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ }
+ }
+
+ @Test
+ public void testReadKernelUidCpuTimesLocked_invalidUid() {
+ //PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+ final int testUserId = 11;
+ final int invalidUserId = 15;
+ final int invalidUid = UserHandle.getUid(invalidUserId, FIRST_APPLICATION_UID + 99);
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ when(mUserInfoProvider.exists(invalidUserId)).thenReturn(false);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ FIRST_APPLICATION_UID + 27,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[][] uidTimesUs = {
+ {12, 34}, {34897394, 3123983}, {79775429834l, 8430434903489l}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ }
+ // And one for the invalid uid
+ callback.onUidCpuTime(invalidUid, 3879, 239);
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(null, null);
+
+ // VERIFY
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ uidTimesUs[i][0], u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ uidTimesUs[i][1], u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ }
+ assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
+ mBatteryStatsImpl.getUidStats().get(invalidUid));
+ verify(mKernelUidCpuTimeReader).removeUid(invalidUid);
+ }
+
+ @Test
+ public void testReadKernelUidCpuTimesLocked_withPartialTimers() {
+ //PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+ final int testUserId = 11;
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ FIRST_APPLICATION_UID + 27,
+ FIRST_APPLICATION_UID + 33
+ });
+ final int[] partialTimerUids = {
+ UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 48),
+ UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 10)
+ };
+ final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers
+ = getPartialTimers(partialTimerUids);
+ final long[][] uidTimesUs = {
+ {12, 34}, {3394, 3123}, {7977, 80434}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuTimeReader.Callback callback =
+ (KernelUidCpuTimeReader.Callback) invocation.getArguments()[0];
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuTime(testUids[i], uidTimesUs[i][0], uidTimesUs[i][1]);
+ }
+ return null;
+ }).when(mKernelUidCpuTimeReader).readDelta(any(KernelUidCpuTimeReader.Callback.class));
+
+ // RUN
+ final SparseLongArray updatedUids = new SparseLongArray();
+ mBatteryStatsImpl.readKernelUidCpuTimesLocked(partialTimers, updatedUids);
+
+ // VERIFY
+ long totalUserTimeUs = 0;
+ long totalSystemTimeUs = 0;
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ final long expectedUserTimeUs = uidTimesUs[i][0] * WAKE_LOCK_WEIGHT / 100;
+ final long expectedSystemTimeUs = uidTimesUs[i][1] * WAKE_LOCK_WEIGHT / 100;
+ assertEquals("Unexpected user cpu time for uid=" + testUids[i],
+ expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for uid=" + testUids[i],
+ expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected entry in updated uids for uid=" + testUids[i],
+ expectedUserTimeUs + expectedSystemTimeUs, updatedUids.get(testUids[i]));
+ updatedUids.delete(testUids[i]);
+ totalUserTimeUs += uidTimesUs[i][0];
+ totalSystemTimeUs += uidTimesUs[i][1];
+ }
+
+ totalUserTimeUs = totalUserTimeUs * (100 - WAKE_LOCK_WEIGHT) / 100;
+ totalSystemTimeUs = totalSystemTimeUs * (100 - WAKE_LOCK_WEIGHT) / 100;
+ for (int i = 0; i < partialTimerUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(partialTimerUids[i]);
+ assertNotNull("No entry for partial timer uid=" + partialTimerUids[i], u);
+ final long expectedUserTimeUs = totalUserTimeUs / (partialTimerUids.length - i);
+ final long expectedSystemTimeUs = totalSystemTimeUs / (partialTimerUids.length - i);
+ assertEquals("Unexpected user cpu time for partial timer uid=" + partialTimerUids[i],
+ expectedUserTimeUs, u.getUserCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for partial timer uid=" + partialTimerUids[i],
+ expectedSystemTimeUs, u.getSystemCpuTimeUs(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected entry in updated uids for partial timer uid="
+ + partialTimerUids[i],
+ expectedUserTimeUs + expectedSystemTimeUs,
+ updatedUids.get(partialTimerUids[i]));
+ updatedUids.delete(partialTimerUids[i]);
+ totalUserTimeUs -= expectedUserTimeUs;
+ totalSystemTimeUs -= expectedSystemTimeUs;
+
+ final BatteryStats.Uid.Proc proc = u.getProcessStats().get("*wakelock*");
+ assertEquals("Unexpected user cpu time for *wakelock* in uid=" + partialTimerUids[i],
+ expectedUserTimeUs / 1000, proc.getUserTime(STATS_SINCE_CHARGED));
+ assertEquals("Unexpected system cpu time for *wakelock* in uid=" + partialTimerUids[i],
+ expectedSystemTimeUs / 1000, proc.getSystemTime(STATS_SINCE_CHARGED));
+ }
+ assertEquals(0, totalUserTimeUs);
+ assertEquals(0, totalSystemTimeUs);
+ assertEquals("Updated uids: " + updatedUids, 0, updatedUids.size());
+ }
+
+ @Test
+ public void testReadKernelUidCpuFreqTimesLocked() {
+ // PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+ final int testUserId = 11;
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ FIRST_APPLICATION_UID + 27,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[] freqs = {1, 12, 123, 12, 1234};
+ final long[][] uidTimesMs = {
+ {4, 10, 5, 9, 4},
+ {5, 1, 12, 2, 10},
+ {8, 25, 3, 0, 42}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuFreqTimeReader.Callback callback =
+ (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ callback.onCpuFreqs(freqs);
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ }
+ return null;
+ }).when(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+ // VERIFY
+ assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+
+ assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+ uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+ assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+ }
+
+ // Repeat the test when the screen is off.
+
+ // PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, true, 0, 0);
+ final long[][] deltasMs = {
+ {3, 12, 55, 100, 32},
+ {3248327490475l, 232349349845043l, 123, 2398, 0},
+ {43, 3345, 2143, 123, 4554}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuFreqTimeReader.Callback callback =
+ (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ callback.onCpuFreqs(freqs);
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ }
+ return null;
+ }).when(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+ // VERIFY
+ assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+
+ assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+ sum(uidTimesMs[i], deltasMs[i]), u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+ assertArrayEquals("Unexpected screen-off cpu times for uid=" + testUids[i],
+ deltasMs[i], u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+ }
+ }
+
+ @Test
+ public void testReadKernelUidCpuFreqTimesLocked_isolatedUid() {
+ // PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+ final int testUserId = 11;
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ final int isolatedAppId = FIRST_ISOLATED_UID + 27;
+ final int isolatedUid = UserHandle.getUid(testUserId, isolatedAppId);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ isolatedAppId,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[] freqs = {1, 12, 123, 12, 1234};
+ final long[][] uidTimesMs = {
+ {4, 10, 5, 9, 4},
+ {5, 1, 12, 2, 10},
+ {8, 25, 3, 0, 42}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuFreqTimeReader.Callback callback =
+ (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ callback.onCpuFreqs(freqs);
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ }
+ return null;
+ }).when(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+ // VERIFY
+ assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ if (UserHandle.isIsolated(testUids[i])) {
+ assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+ continue;
+ }
+ assertNotNull("No entry for uid=" + testUids[i], u);
+
+ assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+ uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+ assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+ }
+ verify(mKernelUidCpuFreqTimeReader).removeUid(isolatedUid);
+
+
+ // Add an isolated uid mapping and repeat the test.
+
+ // PRECONDITIONS
+ final int ownerUid = UserHandle.getUid(testUserId, FIRST_APPLICATION_UID + 42);
+ mBatteryStatsImpl.addIsolatedUidLocked(isolatedUid, ownerUid);
+ final long[][] deltasMs = {
+ {3, 12, 55, 100, 32},
+ {32483274, 232349349, 123, 2398, 0},
+ {43, 3345, 2143, 123, 4554}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuFreqTimeReader.Callback callback =
+ (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ callback.onCpuFreqs(freqs);
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuFreqTime(testUids[i], deltasMs[i]);
+ }
+ return null;
+ }).when(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+ // VERIFY
+ assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+ for (int i = 0; i < testUids.length; ++i) {
+ BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ final long[] expectedTimes;
+ if (UserHandle.isIsolated(testUids[i])) {
+ assertNull("There shouldn't be an entry for isolated uid=" + testUids[i], u);
+ // Since we added a mapping, an entry should've been created for owner uid.
+ u = mBatteryStatsImpl.getUidStats().get(ownerUid);
+ expectedTimes = deltasMs[i];
+ assertNotNull("No entry for owner uid=" + ownerUid, u);
+ } else {
+ assertNotNull("No entry for uid=" + testUids[i], u);
+ expectedTimes = sum(uidTimesMs[i], deltasMs[i]);
+ }
+
+ assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+ expectedTimes, u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+ assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+ }
+ }
+
+ @Test
+ public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+ // PRECONDITIONS
+ mBatteryStatsImpl.updateTimeBasesLocked(true, false, 0, 0);
+
+ final int testUserId = 11;
+ final int invalidUserId = 15;
+ final int invalidUid = UserHandle.getUid(invalidUserId, FIRST_APPLICATION_UID + 99);
+ when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
+ when(mUserInfoProvider.exists(invalidUserId)).thenReturn(false);
+ final int[] testUids = getUids(testUserId, new int[] {
+ FIRST_APPLICATION_UID + 22,
+ FIRST_APPLICATION_UID + 27,
+ FIRST_APPLICATION_UID + 33
+ });
+ final long[] freqs = {1, 12, 123, 12, 1234};
+ final long[][] uidTimesMs = {
+ {4, 10, 5, 9, 4},
+ {5, 1, 12, 2, 10},
+ {8, 25, 3, 0, 42}
+ };
+ doAnswer(invocation -> {
+ final KernelUidCpuFreqTimeReader.Callback callback =
+ (KernelUidCpuFreqTimeReader.Callback) invocation.getArguments()[0];
+ callback.onCpuFreqs(freqs);
+ for (int i = 0; i < testUids.length; ++i) {
+ callback.onUidCpuFreqTime(testUids[i], uidTimesMs[i]);
+ }
+ // And one for the invalid uid
+ callback.onUidCpuFreqTime(invalidUid, new long[] {12, 839, 32, 34, 21});
+ return null;
+ }).when(mKernelUidCpuFreqTimeReader).readDelta(
+ any(KernelUidCpuFreqTimeReader.Callback.class));
+
+ // RUN
+ mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked();
+
+ // VERIFY
+ assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs());
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+ assertNotNull("No entry for uid=" + testUids[i], u);
+
+ assertArrayEquals("Unexpected cpu times for uid=" + testUids[i],
+ uidTimesMs[i], u.getCpuFreqTimes(STATS_SINCE_CHARGED));
+ assertNull("Unexpected screen-off cpu times for uid=" + testUids[i],
+ u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED));
+ }
+ assertNull("There shouldn't be an entry for invalid uid=" + invalidUid,
+ mBatteryStatsImpl.getUidStats().get(invalidUid));
+ verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
+ }
+
+ private void initKernelCpuSpeedReaders(int count) {
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[count];
+ for (int i = 0; i < count; ++i) {
+ mKernelCpuSpeedReaders[i] = Mockito.mock(KernelCpuSpeedReader.class);
+ }
+ mBatteryStatsImpl.setKernelCpuSpeedReaders(mKernelCpuSpeedReaders);
+ }
+
+ private ArrayList<BatteryStatsImpl.StopwatchTimer> getPartialTimers(int... uids) {
+ final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers = new ArrayList<>();
+ final BatteryStatsImpl.TimeBase timeBase = new BatteryStatsImpl.TimeBase();
+ for (int uid : uids) {
+ final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uid);
+ final BatteryStatsImpl.StopwatchTimer timer = new BatteryStatsImpl.StopwatchTimer(
+ mClocks, u, WAKE_TYPE_PARTIAL, null, timeBase);
+ partialTimers.add(timer);
+ }
+ return partialTimers;
+ }
+
+ private long[] sum(long[] a, long[] b) {
+ assertEquals("Arrays a: " + Arrays.toString(a) + ", b: " + Arrays.toString(b),
+ a.length, b.length);
+ final long[] result = new long[a.length];
+ for (int i = 0; i < a.length; ++i) {
+ result[i] = a[i] + b[i];
+ }
+ return result;
+ }
+
+ private int[] getUids(int userId, int[] appIds) {
+ final int[] uids = new int[appIds.length];
+ for (int i = appIds.length - 1; i >= 0; --i) {
+ uids[i] = UserHandle.getUid(userId, appIds[i]);
+ }
+ return uids;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index f171afc..c1ca864 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -20,6 +20,7 @@
@RunWith(Suite.class)
@Suite.SuiteClasses({
+ BatteryStatsCpuTimesTest.class,
BatteryStatsBackgroundStatsTest.class,
BatteryStatsCounterTest.class,
BatteryStatsDualTimerTest.class,
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 a123fce..73ee3e5 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 java.util.ArrayList;
+
/**
* Mocks a BatteryStatsImpl object.
*/
@@ -48,5 +50,45 @@
public TimeBase getOnBatteryScreenOffBackgroundTimeBase(int uid) {
return getUidStatsLocked(uid).mOnBatteryScreenOffBackgroundTimeBase;
}
+
+ public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) {
+ mPowerProfile = powerProfile;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setKernelUidCpuFreqTimeReader(KernelUidCpuFreqTimeReader reader) {
+ mKernelUidCpuFreqTimeReader = reader;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setKernelUidCpuTimeReader(KernelUidCpuTimeReader reader) {
+ mKernelUidCpuTimeReader = reader;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
+ mKernelCpuSpeedReaders = readers;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setUserInfoProvider(UserInfoProvider provider) {
+ mUserInfoProvider = provider;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setPartialTimers(ArrayList<StopwatchTimer> partialTimers) {
+ mPartialTimers = partialTimers;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setLastPartialTimers(ArrayList<StopwatchTimer> lastPartialTimers) {
+ mLastPartialTimers = lastPartialTimers;
+ return this;
+ }
+
+ public MockBatteryStatsImpl setOnBatteryInternal(boolean onBatteryInternal) {
+ mOnBatteryInternal = onBatteryInternal;
+ return this;
+ }
}
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 2a10bdd..b3bba07 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -15,7 +15,7 @@
#
# Generic key layout file for full alphabetic US English PC style external keyboards.
#
-# This file is intentionally very generic and is intended to support a broad rang of keyboards.
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
# Do not edit the generic key layout to support a specific keyboard; instead, create
# a new key layout file with the required keyboard configuration.
#
diff --git a/data/keyboards/Generic_Iot.kl b/data/keyboards/Generic_Iot.kl
new file mode 100644
index 0000000..89df224
--- /dev/null
+++ b/data/keyboards/Generic_Iot.kl
@@ -0,0 +1,443 @@
+# 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.
+
+#
+# Generic key layout file for full alphabetic US English PC style external keyboards.
+#
+# This file is intentionally very generic and is intended to support a broad range of keyboards.
+# Do not edit the generic key layout to support a specific keyboard; instead, create
+# a new key layout file with the required keyboard configuration.
+#
+
+key 1 ESCAPE
+key 2 1
+key 3 2
+key 4 3
+key 5 4
+key 6 5
+key 7 6
+key 8 7
+key 9 8
+key 10 9
+key 11 0
+key 12 MINUS
+key 13 EQUALS
+key 14 DEL
+key 15 TAB
+key 16 Q
+key 17 W
+key 18 E
+key 19 R
+key 20 T
+key 21 Y
+key 22 U
+key 23 I
+key 24 O
+key 25 P
+key 26 LEFT_BRACKET
+key 27 RIGHT_BRACKET
+key 28 ENTER
+key 29 CTRL_LEFT
+key 30 A
+key 31 S
+key 32 D
+key 33 F
+key 34 G
+key 35 H
+key 36 J
+key 37 K
+key 38 L
+key 39 SEMICOLON
+key 40 APOSTROPHE
+key 41 GRAVE
+key 42 SHIFT_LEFT
+key 43 BACKSLASH
+key 44 Z
+key 45 X
+key 46 C
+key 47 V
+key 48 B
+key 49 N
+key 50 M
+key 51 COMMA
+key 52 PERIOD
+key 53 SLASH
+key 54 SHIFT_RIGHT
+key 55 NUMPAD_MULTIPLY
+key 56 ALT_LEFT
+key 57 SPACE
+key 58 CAPS_LOCK
+key 59 F1
+key 60 F2
+key 61 F3
+key 62 F4
+key 63 F5
+key 64 F6
+key 65 F7
+key 66 F8
+key 67 F9
+key 68 F10
+key 69 NUM_LOCK
+key 70 SCROLL_LOCK
+key 71 NUMPAD_7
+key 72 NUMPAD_8
+key 73 NUMPAD_9
+key 74 NUMPAD_SUBTRACT
+key 75 NUMPAD_4
+key 76 NUMPAD_5
+key 77 NUMPAD_6
+key 78 NUMPAD_ADD
+key 79 NUMPAD_1
+key 80 NUMPAD_2
+key 81 NUMPAD_3
+key 82 NUMPAD_0
+key 83 NUMPAD_DOT
+# key 84 (undefined)
+key 85 ZENKAKU_HANKAKU
+key 86 BACKSLASH
+key 87 F11
+key 88 F12
+key 89 RO
+# key 90 "KEY_KATAKANA"
+# key 91 "KEY_HIRAGANA"
+key 92 HENKAN
+key 93 KATAKANA_HIRAGANA
+key 94 MUHENKAN
+key 95 NUMPAD_COMMA
+key 96 NUMPAD_ENTER
+key 97 CTRL_RIGHT
+key 98 NUMPAD_DIVIDE
+key 99 SYSRQ
+key 100 ALT_RIGHT
+# key 101 "KEY_LINEFEED"
+key 102 MOVE_HOME
+key 103 DPAD_UP
+key 104 PAGE_UP
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 107 MOVE_END
+key 108 DPAD_DOWN
+key 109 PAGE_DOWN
+key 110 INSERT
+key 111 FORWARD_DEL
+# key 112 "KEY_MACRO"
+key 113 VOLUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+key 116 POWER
+key 117 NUMPAD_EQUALS
+# key 118 "KEY_KPPLUSMINUS"
+key 119 BREAK
+# key 120 (undefined)
+key 121 NUMPAD_COMMA
+key 122 KANA
+key 123 EISU
+key 124 YEN
+key 125 META_LEFT
+key 126 META_RIGHT
+key 127 MENU
+key 128 MEDIA_STOP
+# key 129 "KEY_AGAIN"
+# key 130 "KEY_PROPS"
+# key 131 "KEY_UNDO"
+# key 132 "KEY_FRONT"
+key 133 COPY
+# key 134 "KEY_OPEN"
+key 135 PASTE
+# key 136 "KEY_FIND"
+key 137 CUT
+# key 138 "KEY_HELP"
+key 139 MENU
+key 140 CALCULATOR
+# key 141 "KEY_SETUP"
+key 142 SLEEP
+key 143 WAKEUP
+# key 144 "KEY_FILE"
+# key 145 "KEY_SENDFILE"
+# key 146 "KEY_DELETEFILE"
+# key 147 "KEY_XFER"
+# key 148 "KEY_PROG1"
+# key 149 "KEY_PROG2"
+key 150 EXPLORER
+# key 151 "KEY_MSDOS"
+key 152 POWER
+# key 153 "KEY_DIRECTION"
+# key 154 "KEY_CYCLEWINDOWS"
+key 155 ENVELOPE
+key 156 BOOKMARK
+# key 157 "KEY_COMPUTER"
+key 158 BACK
+key 159 FORWARD
+key 160 MEDIA_CLOSE
+key 161 MEDIA_EJECT
+key 162 MEDIA_EJECT
+key 163 MEDIA_NEXT
+key 164 MEDIA_PLAY_PAUSE
+key 165 MEDIA_PREVIOUS
+key 166 MEDIA_STOP
+key 167 MEDIA_RECORD
+key 168 MEDIA_REWIND
+key 169 CALL
+# key 170 "KEY_ISO"
+key 171 MUSIC
+key 172 HOME
+# key 173 "KEY_REFRESH"
+# key 174 "KEY_EXIT"
+# key 175 "KEY_MOVE"
+# key 176 "KEY_EDIT"
+key 177 PAGE_UP
+key 178 PAGE_DOWN
+key 179 NUMPAD_LEFT_PAREN
+key 180 NUMPAD_RIGHT_PAREN
+# key 181 "KEY_NEW"
+# key 182 "KEY_REDO"
+# key 183 F13
+# key 184 F14
+# key 185 F15
+# key 186 F16
+# key 187 F17
+# key 188 F18
+# key 189 F19
+# key 190 F20
+# key 191 F21
+# key 192 F22
+# key 193 F23
+# key 194 F24
+# key 195 (undefined)
+# key 196 (undefined)
+# key 197 (undefined)
+# key 198 (undefined)
+# key 199 (undefined)
+key 200 MEDIA_PLAY
+key 201 MEDIA_PAUSE
+# key 202 "KEY_PROG3"
+# key 203 "KEY_PROG4"
+# key 204 (undefined)
+# key 205 "KEY_SUSPEND"
+# key 206 "KEY_CLOSE"
+key 207 MEDIA_PLAY
+key 208 MEDIA_FAST_FORWARD
+# key 209 "KEY_BASSBOOST"
+# key 210 "KEY_PRINT"
+# key 211 "KEY_HP"
+key 212 CAMERA
+key 213 MUSIC
+# key 214 "KEY_QUESTION"
+key 215 ENVELOPE
+# key 216 "KEY_CHAT"
+key 217 SEARCH
+# key 218 "KEY_CONNECT"
+# key 219 "KEY_FINANCE"
+# key 220 "KEY_SPORT"
+# key 221 "KEY_SHOP"
+# key 222 "KEY_ALTERASE"
+# key 223 "KEY_CANCEL"
+key 224 BRIGHTNESS_DOWN
+key 225 BRIGHTNESS_UP
+key 226 HEADSETHOOK
+key 227 POUND
+key 228 STAR
+
+key 256 BUTTON_1
+key 257 BUTTON_2
+key 258 BUTTON_3
+key 259 BUTTON_4
+key 260 BUTTON_5
+key 261 BUTTON_6
+key 262 BUTTON_7
+key 263 BUTTON_8
+key 264 BUTTON_9
+key 265 BUTTON_10
+key 266 BUTTON_11
+key 267 BUTTON_12
+key 268 BUTTON_13
+key 269 BUTTON_14
+key 270 BUTTON_15
+key 271 BUTTON_16
+
+key 288 BUTTON_1
+key 289 BUTTON_2
+key 290 BUTTON_3
+key 291 BUTTON_4
+key 292 BUTTON_5
+key 293 BUTTON_6
+key 294 BUTTON_7
+key 295 BUTTON_8
+key 296 BUTTON_9
+key 297 BUTTON_10
+key 298 BUTTON_11
+key 299 BUTTON_12
+key 300 BUTTON_13
+key 301 BUTTON_14
+key 302 BUTTON_15
+key 303 BUTTON_16
+
+
+key 304 BUTTON_A
+key 305 BUTTON_B
+key 306 BUTTON_C
+key 307 BUTTON_X
+key 308 BUTTON_Y
+key 309 BUTTON_Z
+key 310 BUTTON_L1
+key 311 BUTTON_R1
+key 312 BUTTON_L2
+key 313 BUTTON_R2
+key 314 BUTTON_SELECT
+key 315 BUTTON_START
+key 316 BUTTON_MODE
+key 317 BUTTON_THUMBL
+key 318 BUTTON_THUMBR
+
+
+# key 352 "KEY_OK"
+key 353 DPAD_CENTER
+# key 354 "KEY_GOTO"
+# key 355 "KEY_CLEAR"
+# key 356 "KEY_POWER2"
+# key 357 "KEY_OPTION"
+# key 358 "KEY_INFO"
+# key 359 "KEY_TIME"
+# key 360 "KEY_VENDOR"
+# key 361 "KEY_ARCHIVE"
+key 362 GUIDE
+# key 363 "KEY_CHANNEL"
+# key 364 "KEY_FAVORITES"
+# key 365 "KEY_EPG"
+key 366 DVR
+# key 367 "KEY_MHP"
+# key 368 "KEY_LANGUAGE"
+# key 369 "KEY_TITLE"
+# key 370 "KEY_SUBTITLE"
+# key 371 "KEY_ANGLE"
+# key 372 "KEY_ZOOM"
+# key 373 "KEY_MODE"
+# key 374 "KEY_KEYBOARD"
+# key 375 "KEY_SCREEN"
+# key 376 "KEY_PC"
+key 377 TV
+# key 378 "KEY_TV2"
+# key 379 "KEY_VCR"
+# key 380 "KEY_VCR2"
+# key 381 "KEY_SAT"
+# key 382 "KEY_SAT2"
+# key 383 "KEY_CD"
+# key 384 "KEY_TAPE"
+# key 385 "KEY_RADIO"
+# key 386 "KEY_TUNER"
+# key 387 "KEY_PLAYER"
+# key 388 "KEY_TEXT"
+# key 389 "KEY_DVD"
+# key 390 "KEY_AUX"
+# key 391 "KEY_MP3"
+# key 392 "KEY_AUDIO"
+# key 393 "KEY_VIDEO"
+# key 394 "KEY_DIRECTORY"
+# key 395 "KEY_LIST"
+# key 396 "KEY_MEMO"
+key 397 CALENDAR
+# key 398 "KEY_RED"
+# key 399 "KEY_GREEN"
+# key 400 "KEY_YELLOW"
+# key 401 "KEY_BLUE"
+key 402 CHANNEL_UP
+key 403 CHANNEL_DOWN
+# key 404 "KEY_FIRST"
+# key 405 "KEY_LAST"
+# key 406 "KEY_AB"
+# key 407 "KEY_NEXT"
+# key 408 "KEY_RESTART"
+# key 409 "KEY_SLOW"
+# key 410 "KEY_SHUFFLE"
+# key 411 "KEY_BREAK"
+# key 412 "KEY_PREVIOUS"
+# key 413 "KEY_DIGITS"
+# key 414 "KEY_TEEN"
+# key 415 "KEY_TWEN"
+
+key 429 CONTACTS
+
+# key 448 "KEY_DEL_EOL"
+# key 449 "KEY_DEL_EOS"
+# key 450 "KEY_INS_LINE"
+# key 451 "KEY_DEL_LINE"
+
+
+key 464 FUNCTION
+key 465 ESCAPE FUNCTION
+key 466 F1 FUNCTION
+key 467 F2 FUNCTION
+key 468 F3 FUNCTION
+key 469 F4 FUNCTION
+key 470 F5 FUNCTION
+key 471 F6 FUNCTION
+key 472 F7 FUNCTION
+key 473 F8 FUNCTION
+key 474 F9 FUNCTION
+key 475 F10 FUNCTION
+key 476 F11 FUNCTION
+key 477 F12 FUNCTION
+key 478 1 FUNCTION
+key 479 2 FUNCTION
+key 480 D FUNCTION
+key 481 E FUNCTION
+key 482 F FUNCTION
+key 483 S FUNCTION
+key 484 B FUNCTION
+
+
+# key 497 KEY_BRL_DOT1
+# key 498 KEY_BRL_DOT2
+# key 499 KEY_BRL_DOT3
+# key 500 KEY_BRL_DOT4
+# key 501 KEY_BRL_DOT5
+# key 502 KEY_BRL_DOT6
+# key 503 KEY_BRL_DOT7
+# key 504 KEY_BRL_DOT8
+
+key 580 APP_SWITCH
+key 582 VOICE_ASSIST
+
+# Keys defined by HID usages
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+
+# Joystick and game controller axes.
+# Axes that are not mapped will be assigned generic axis numbers by the input subsystem.
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x03 RX
+axis 0x04 RY
+axis 0x05 RZ
+axis 0x06 THROTTLE
+axis 0x07 RUDDER
+axis 0x08 WHEEL
+axis 0x09 GAS
+axis 0x0a BRAKE
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+# LEDs
+led 0x00 NUM_LOCK
+led 0x01 CAPS_LOCK
+led 0x02 SCROLL_LOCK
+led 0x03 COMPOSE
+led 0x04 KANA
+led 0x05 SLEEP
+led 0x06 SUSPEND
+led 0x07 MUTE
+led 0x08 MISC
+led 0x09 MAIL
+led 0x0a CHARGING
diff --git a/dirty-image-objects b/dirty-image-objects
new file mode 100644
index 0000000..9b4d199
--- /dev/null
+++ b/dirty-image-objects
@@ -0,0 +1,176 @@
+#
+# 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.
+#
+#
+#
+# Dirty-image-objects file for boot image.
+#
+# Objects in this file are known dirty at runtime. Current this includes:
+# - classes with known dirty static fields.
+#
+# The image writer will bin these objects together in the image.
+#
+# This file can be generated using imgdiag with a command such as:
+# adb shell imgdiag --image-diff-pid=<app pid> --zygote-diff-pid=<zygote pid> \
+# --boot-image=/system/framework/boot.art --dump-dirty-objects
+# Then, grep for lines containing "Private dirty object" from the output.
+# This particular file was generated by dumping systemserver and systemui.
+#
+java.lang.System
+java.net.Inet4Address
+java.lang.Thread
+java.lang.Throwable
+java.util.Collections
+javax.net.ssl.SSLContext
+java.nio.charset.Charset
+java.security.Provider
+javax.net.ssl.HttpsURLConnection
+javax.net.ssl.SSLSocketFactory
+java.util.TimeZone
+java.util.Locale
+java.util.function.ToIntFunction
+sun.misc.FormattedFloatingDecimal
+java.util.stream.IntStream
+android.icu.util.TimeZone
+libcore.io.DropBox
+org.apache.harmony.luni.internal.util.TimezoneGetter
+dalvik.system.SocketTagger
+dalvik.system.CloseGuard
+java.lang.ref.FinalizerReference
+com.android.org.conscrypt.ct.CTLogStoreImpl
+com.android.org.conscrypt.SSLParametersImpl
+com.android.org.conscrypt.OpenSSLContextImpl
+com.android.org.conscrypt.SSLParametersImpl$AliasChooser
+com.android.org.conscrypt.SSLParametersImpl$PSKCallbacks
+com.android.org.conscrypt.NativeCrypto$SSLHandshakeCallbacks
+com.android.okhttp.OkHttpClient
+com.android.okhttp.okio.SegmentPool
+com.android.okhttp.okio.AsyncTimeout
+com.android.okhttp.HttpUrl
+android.os.StrictMode
+com.android.internal.os.BinderInternal
+android.os.storage.StorageManager
+android.os.Trace
+android.app.ActivityManager
+android.media.MediaRouter
+android.os.Environment
+android.view.ThreadedRenderer
+android.media.AudioManager
+android.app.AlarmManager
+android.telephony.TelephonyManager
+android.bluetooth.BluetoothAdapter
+com.android.internal.os.SomeArgs
+android.os.LocaleList
+android.view.WindowManagerGlobal
+android.media.AudioSystem
+android.ddm.DdmHandleAppName
+android.provider.Settings
+android.view.ViewRootImpl
+android.net.ConnectivityManager
+android.app.ActivityThread
+android.os.BaseBundle
+android.util.ArraySet
+android.view.View
+android.os.ServiceManager
+android.view.ViewTreeObserver
+android.hardware.input.InputManager
+android.os.UEventObserver
+android.app.NotificationManager
+android.hardware.display.DisplayManagerGlobal
+android.os.Binder
+android.app.AppOpsManager
+android.content.ContentResolver
+android.app.backup.BackupManager
+android.util.ArrayMap
+android.os.Looper
+android.graphics.Bitmap
+android.view.textservice.TextServicesManager
+com.android.internal.inputmethod.InputMethodUtils
+android.app.QueuedWork
+android.graphics.TemporaryBuffer
+android.widget.ImageView
+android.database.sqlite.SQLiteGlobal
+android.view.autofill.Helper
+android.text.method.SingleLineTransformationMethod
+com.android.internal.os.RuntimeInit
+android.view.inputmethod.InputMethodManager
+android.hardware.SystemSensorManager
+android.database.CursorWindow
+android.text.TextUtils
+android.media.PlayerBase
+android.app.ResourcesManager
+android.os.Message
+android.view.accessibility.AccessibilityManager
+android.app.Notification
+android.provider.ContactsContract$ContactNameColumns
+android.provider.CalendarContract$EventsColumns
+android.provider.CalendarContract$CalendarColumns
+android.provider.CalendarContract$SyncColumns
+android.provider.ContactsContract$ContactsColumns
+android.content.pm.PackageManager$OnPermissionsChangedListener
+android.net.IpConfiguration$ProxySettings
+android.provider.ContactsContract$ContactOptionsColumns
+android.net.wifi.SupplicantState
+android.provider.ContactsContract$ContactStatusColumns
+android.view.accessibility.AccessibilityManager$TouchExplorationStateChangeListener
+android.provider.CalendarContract$CalendarSyncColumns
+android.bluetooth.BluetoothProfile$ServiceListener
+android.provider.ContactsContract$ContactCounts
+android.net.IpConfiguration$IpAssignment
+android.text.TextWatcher
+android.graphics.Bitmap$CompressFormat
+android.location.LocationListener
+sun.security.jca.Providers
+java.lang.CharSequence
+android.icu.util.ULocale
+dalvik.system.BaseDexClassLoader
+android.icu.text.BreakIterator
+libcore.io.EventLogger
+libcore.net.NetworkSecurityPolicy
+android.icu.text.UnicodeSet
+com.android.org.conscrypt.TrustedCertificateStore$PreloadHolder
+android.app.SearchManager
+android.os.Build
+android.app.ContextImpl
+android.app.WallpaperManager
+android.security.net.config.ApplicationConfig
+android.animation.LayoutTransition
+android.widget.TextView
+com.android.internal.logging.MetricsLogger
+android.renderscript.RenderScriptCacheDir
+android.os.Process
+android.os.Handler
+android.content.Context
+android.graphics.drawable.AdaptiveIconDrawable
+android.provider.FontsContract
+android.text.style.SuggestionSpan
+android.graphics.drawable.VectorDrawable$VGroup
+android.view.ViewStub
+android.text.style.MetricAffectingSpan
+android.content.SharedPreferences$OnSharedPreferenceChangeListener
+android.app.PendingIntent
+android.text.SpanWatcher
+android.widget.FrameLayout
+android.net.NetworkRequest$Type
+android.net.NetworkInfo$State
+android.graphics.drawable.GradientDrawable
+android.text.style.AlignmentSpan
+android.widget.LinearLayout
+android.text.style.CharacterStyle
+android.view.View$OnApplyWindowInsetsListener
+android.view.MenuItem
+android.text.style.ReplacementSpan
+android.graphics.drawable.Icon
+android.widget.Button
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 16eab45..aa9227c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2747,7 +2747,7 @@
* @param offset index of caret position
* @return width measurement between start and offset
*/
- public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart,
+ public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset) {
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 1d8b583..3a8dfb0 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -548,29 +548,7 @@
final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mWeight : mWeight;
final boolean italic =
(mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
- final int key = weight << 1 | (italic ? 1 : 0);
-
- Typeface typeface;
- synchronized(sLock) {
- SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
- if (innerCache != null) {
- typeface = innerCache.get(key);
- if (typeface != null) {
- return typeface;
- }
- }
-
- typeface = new Typeface(
- nativeCreateFromTypefaceWithExactStyle(
- base.native_instance, weight, italic));
-
- if (innerCache == null) {
- innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
- sTypefaceCache.put(base.native_instance, innerCache);
- }
- innerCache.put(key, typeface);
- }
- return typeface;
+ return createWeightStyle(base, weight, italic);
}
/**
@@ -688,7 +666,8 @@
* style from the same family of an existing typeface object. If family is
* null, this selects from the default font's family.
*
- * @param family May be null. The name of the existing type face.
+ * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+ * typeface is used instead.
* @param style The style (normal, bold, italic) of the typeface.
* e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
* @return The best matching typeface.
@@ -727,6 +706,53 @@
return typeface;
}
+ /**
+ * Creates a typeface object that best matches the specified existing typeface and the specified
+ * weight and italic style
+ *
+ * @param family An existing {@link Typeface} object. In case of {@code null}, the default
+ * typeface is used instead.
+ * @param weight The desired weight to be drawn.
+ * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
+ * @return A {@link Typeface} object for drawing specified weight and italic style. Never
+ * returns {@code null}
+ */
+ public static @NonNull Typeface create(@Nullable Typeface family,
+ @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+ Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
+ if (family == null) {
+ family = sDefaultTypeface;
+ }
+ return createWeightStyle(family, weight, italic);
+ }
+
+ private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
+ @IntRange(from = 1, to = 1000) int weight, boolean italic) {
+ final int key = weight << 1 | (italic ? 1 : 0);
+
+ Typeface typeface;
+ synchronized(sLock) {
+ SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
+ if (innerCache != null) {
+ typeface = innerCache.get(key);
+ if (typeface != null) {
+ return typeface;
+ }
+ }
+
+ typeface = new Typeface(
+ nativeCreateFromTypefaceWithExactStyle(
+ base.native_instance, weight, italic));
+
+ if (innerCache == null) {
+ innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
+ sTypefaceCache.put(base.native_instance, innerCache);
+ }
+ innerCache.put(key, typeface);
+ }
+ return typeface;
+ }
+
/** @hide */
public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
@NonNull List<FontVariationAxis> axes) {
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 55eeb7f..61b3876 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -241,7 +241,8 @@
if (CC_LIKELY(layerType != LayerType::RenderLayer)
|| CC_UNLIKELY(!isRenderable())
|| CC_UNLIKELY(properties().getWidth() == 0)
- || CC_UNLIKELY(properties().getHeight() == 0)) {
+ || CC_UNLIKELY(properties().getHeight() == 0)
+ || CC_UNLIKELY(!properties().fitsOnLayer())) {
if (CC_UNLIKELY(hasLayer())) {
renderthread::CanvasContext::destroyLayer(this);
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 02c0e19..9683d06 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -25,6 +25,7 @@
#include <SkCanvasStateUtils.h>
#include <SkColorFilter.h>
+// TODO remove me!
#include <SkColorSpaceXformCanvas.h>
#include <SkDrawable.h>
#include <SkDeque.h>
@@ -47,28 +48,25 @@
return new SkiaCanvas(bitmap);
}
-Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas) {
- return new SkiaCanvas(skiaCanvas);
+Canvas* Canvas::create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB) {
+ return new SkiaCanvas(skiaCanvas, xformToSRGB);
}
SkiaCanvas::SkiaCanvas() {}
-SkiaCanvas::SkiaCanvas(SkCanvas* canvas) : mCanvas(canvas) {}
+SkiaCanvas::SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB)
+ : mCanvas(canvas)
+{
+ LOG_ALWAYS_FATAL_IF(XformToSRGB::kImmediate == xformToSRGB);
+}
SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) {
sk_sp<SkColorSpace> cs = bitmap.refColorSpace();
mCanvasOwned =
std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy));
- if (cs.get() == nullptr || cs->isSRGB()) {
- mCanvas = mCanvasOwned.get();
- } else {
- /** The wrapper is needed if we are drawing into a non-sRGB destination, since
- * we need to transform all colors (not just bitmaps via filters) into the
- * destination's colorspace.
- */
- mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), std::move(cs));
- mCanvas = mCanvasWrapper.get();
- }
+ mCanvasWrapper = SkCreateColorSpaceXformCanvas(mCanvasOwned.get(),
+ cs == nullptr ? SkColorSpace::MakeSRGB() : std::move(cs));
+ mCanvas = mCanvasWrapper.get();
}
SkiaCanvas::~SkiaCanvas() {}
@@ -77,7 +75,6 @@
if (mCanvas != skiaCanvas) {
mCanvas = skiaCanvas;
mCanvasOwned.reset();
- mCanvasWrapper.reset();
}
mSaveStack.reset(nullptr);
mHighContrastText = false;
@@ -91,15 +88,13 @@
sk_sp<SkColorSpace> cs = bitmap.refColorSpace();
std::unique_ptr<SkCanvas> newCanvas =
std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy));
- std::unique_ptr<SkCanvas> newCanvasWrapper;
- if (cs.get() != nullptr && !cs->isSRGB()) {
- newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(), std::move(cs));
- }
+ std::unique_ptr<SkCanvas> newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(),
+ cs == nullptr ? SkColorSpace::MakeSRGB() : std::move(cs));
// deletes the previously owned canvas (if any)
mCanvasOwned = std::move(newCanvas);
mCanvasWrapper = std::move(newCanvasWrapper);
- mCanvas = mCanvasWrapper ? mCanvasWrapper.get() : mCanvasOwned.get();
+ mCanvas = mCanvasWrapper.get();
// clean up the old save stack
mSaveStack.reset(nullptr);
@@ -536,27 +531,13 @@
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
-const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter) {
- /* We don't apply the colorSpace filter if this canvas is already wrapped with
- * a SkColorSpaceXformCanvas since it already takes care of converting the
- * contents of the bitmap into the appropriate colorspace. The mCanvasWrapper
- * should only be used if this canvas is backed by a surface/bitmap that is known
- * to have a non-sRGB colorspace.
- */
- if (!mCanvasWrapper && colorSpaceFilter) {
+inline static const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
+ sk_sp<SkColorFilter> colorFilter) {
+ if (colorFilter) {
if (origPaint) {
*tmpPaint = *origPaint;
}
-
- if (tmpPaint->getColorFilter()) {
- tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter(
- tmpPaint->refColorFilter(), colorSpaceFilter));
- LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
- } else {
- tmpPaint->setColorFilter(colorSpaceFilter);
- }
-
+ tmpPaint->setColorFilter(colorFilter);
return tmpPaint;
} else {
return origPaint;
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 6a01f96..af2c23e 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -37,8 +37,12 @@
* @param canvas SkCanvas to handle calls made to this SkiaCanvas. Must
* not be NULL. This constructor does not take ownership, so the caller
* must guarantee that it remains valid while the SkiaCanvas is valid.
+ * @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+ * color space before drawing. This makes sense for software rendering.
+ * For the picture case, it may make more sense to leave bitmaps as is,
+ * and handle the xform when replaying the picture.
*/
- explicit SkiaCanvas(SkCanvas* canvas);
+ explicit SkiaCanvas(SkCanvas* canvas, XformToSRGB xformToSRGB);
virtual ~SkiaCanvas();
@@ -178,9 +182,6 @@
void drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode);
- const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter);
-
class Clip;
std::unique_ptr<SkCanvas> mCanvasWrapper; // might own a wrapper on the canvas
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 90d98f2..ac8a081 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -98,6 +98,15 @@
static WARN_UNUSED_RESULT Canvas* create_recording_canvas(int width, int height,
uirenderer::RenderNode* renderNode = nullptr);
+ enum class XformToSRGB {
+ // Transform any Bitmaps to the sRGB color space before drawing.
+ kImmediate,
+
+ // Draw the Bitmap as is. This likely means that we are recording and that the
+ // transform can be handled at playback time.
+ kDefer,
+ };
+
/**
* Create a new Canvas object which delegates to an SkCanvas.
*
@@ -105,10 +114,12 @@
* delegated to this object. This function will call ref() on the
* SkCanvas, and the returned Canvas will unref() it upon
* destruction.
+ * @param xformToSRGB Indicates if bitmaps should be xformed to the sRGB
+ * color space before drawing.
* @return new non-null Canvas Object. The type of DisplayList produced by this canvas is
* determined based on Properties::getRenderPipelineType().
*/
- static Canvas* create_canvas(SkCanvas* skiaCanvas);
+ static Canvas* create_canvas(SkCanvas* skiaCanvas, XformToSRGB xformToSRGB);
/**
* Provides a Skia SkCanvas interface that acts as a proxy to this Canvas.
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index ea302a1..fcd72af 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -17,6 +17,7 @@
#include "GLFunctorDrawable.h"
#include "GlFunctorLifecycleListener.h"
#include "RenderNode.h"
+#include "SkAndroidFrameworkUtils.h"
#include "SkClipStack.h"
#include <private/hwui/DrawGlInfo.h>
#include <GrContext.h>
@@ -49,8 +50,6 @@
return;
}
- canvas->flush();
-
if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
canvas->clear(SK_ColorRED);
return;
@@ -72,33 +71,33 @@
info.height = canvasInfo.height();
mat4.asColMajorf(&info.transform[0]);
+ bool clearStencilAfterFunctor = false;
+
//apply a simple clip with a scissor or a complex clip with a stencil
SkRegion clipRegion;
canvas->temporary_internal_getRgnClip(&clipRegion);
if (CC_UNLIKELY(clipRegion.isComplex())) {
- //It is only a temporary solution to use a scissor to draw the stencil.
- //There is a bug 31489986 to implement efficiently non-rectangular clips.
glDisable(GL_SCISSOR_TEST);
- glDisable(GL_STENCIL_TEST);
- glStencilMask(0xff);
+ glStencilMask(0x1);
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
- glEnable(GL_SCISSOR_TEST);
- SkRegion::Cliperator it(clipRegion, ibounds);
- while (!it.done()) {
- setScissor(info.height, it.rect());
- glClearStencil(0x1);
- glClear(GL_STENCIL_BUFFER_BIT);
- it.next();
+ bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(canvas);
+ canvas->flush();
+ if (stencilWritten) {
+ glStencilMask(0x1);
+ glStencilFunc(GL_EQUAL, 0x1, 0x1);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ clearStencilAfterFunctor = true;
+ glEnable(GL_STENCIL_TEST);
+ } else {
+ glDisable(GL_STENCIL_TEST);
}
- glDisable(GL_SCISSOR_TEST);
- glStencilFunc(GL_EQUAL, 0x1, 0xff);
- glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
- glEnable(GL_STENCIL_TEST);
} else if (clipRegion.isEmpty()) {
+ canvas->flush();
glDisable(GL_STENCIL_TEST);
glDisable(GL_SCISSOR_TEST);
} else {
+ canvas->flush();
glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST);
setScissor(info.height, clipRegion.getBounds());
@@ -106,6 +105,15 @@
(*mFunctor)(DrawGlInfo::kModeDraw, &info);
+ if (clearStencilAfterFunctor) {
+ //clear stencil buffer as it may be used by Skia
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_STENCIL_TEST);
+ glStencilMask(0x1);
+ glClearStencil(0);
+ glClear(GL_STENCIL_BUFFER_BIT);
+ }
+
canvas->getGrContext()->resetContext();
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 4c1d673..bb41607 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -150,17 +150,8 @@
if (origPaint) {
*tmpPaint = *origPaint;
}
-
- sk_sp<SkColorFilter> filter;
- if (colorFilter && tmpPaint->getColorFilter()) {
- filter = SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorFilter);
- LOG_ALWAYS_FATAL_IF(!filter);
- } else {
- filter = colorFilter;
- }
-
tmpPaint->setAntiAlias(false);
- tmpPaint->setColorFilter(filter);
+ tmpPaint->setColorFilter(colorFilter);
return tmpPaint;
} else {
return origPaint;
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index d84b83d..c048dda 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -45,7 +45,8 @@
// record the same text draw into a SkPicture and replay it into a Recording canvas
SkPictureRecorder recorder;
SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0);
- std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas));
+ std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas,
+ Canvas::XformToSRGB::kDefer));
TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25);
sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
@@ -64,7 +65,7 @@
TEST(SkiaCanvas, drawShadowLayer) {
auto surface = SkSurface::MakeRasterN32Premul(10, 10);
- SkiaCanvas canvas(surface->getCanvas());
+ SkiaCanvas canvas(surface->getCanvas(), Canvas::XformToSRGB::kDefer);
// clear to white
canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
@@ -107,14 +108,27 @@
// The result should be less than fully red, since we convert to Adobe RGB at draw time.
ASSERT_EQ(0xFF0000DC, *adobeSkBitmap.getAddr32(0, 0));
- // Test picture recording.
+ // Now try in kDefer mode. This is a little strange given that, in practice, all software
+ // canvases are kImmediate.
+ SkCanvas skCanvas(skBitmap);
+ SkiaCanvas deferCanvas(&skCanvas, Canvas::XformToSRGB::kDefer);
+ deferCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
+ // The result should be as before, since we deferred the conversion to sRGB.
+ ASSERT_EQ(0xFF0000DC, *skBitmap.getAddr32(0, 0));
+
+ // Test picture recording. We will kDefer the xform at recording time, but handle it when
+ // we playback to the software canvas.
SkPictureRecorder recorder;
SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
- SkiaCanvas picCanvas(skPicCanvas);
+ SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer);
picCanvas.drawBitmap(*adobeBitmap, 0, 0, nullptr);
sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
- // Playback to an software canvas. The result should be fully red.
+ // Playback to a deferred canvas. The result should be as before.
+ deferCanvas.asSkCanvas()->drawPicture(picture);
+ ASSERT_EQ(0xFF0000DC, *skBitmap.getAddr32(0, 0));
+
+ // Playback to an immediate canvas. The result should be fully red.
canvas.asSkCanvas()->drawPicture(picture);
ASSERT_EQ(0xFF0000FF, *skBitmap.getAddr32(0, 0));
}
@@ -141,7 +155,7 @@
// Create a picture canvas.
SkPictureRecorder recorder;
SkCanvas* skPicCanvas = recorder.beginRecording(1, 1, NULL, 0);
- SkiaCanvas picCanvas(skPicCanvas);
+ SkiaCanvas picCanvas(skPicCanvas, Canvas::XformToSRGB::kDefer);
state = picCanvas.captureCanvasState();
// Verify that we cannot get the CanvasState.
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 7cdde5f..4c3e261 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2102,9 +2102,7 @@
private int getMimeType(BufferedInputStream in) throws IOException {
in.mark(SIGNATURE_CHECK_SIZE);
byte[] signatureCheckBytes = new byte[SIGNATURE_CHECK_SIZE];
- if (in.read(signatureCheckBytes) != SIGNATURE_CHECK_SIZE) {
- throw new EOFException();
- }
+ in.read(signatureCheckBytes);
in.reset();
if (isJpegFormat(signatureCheckBytes)) {
return IMAGE_TYPE_JPEG;
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7dd70d4..6664456 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -246,7 +246,7 @@
* {@link #OPTION_CLOSEST} often has larger performance overhead compared
* to the other options if there is no sync frame located at timeUs.
*
- * @return A Bitmap containing a representative video frame, which
+ * @return A Bitmap containing a representative video frame, which
* can be null, if such a frame cannot be retrieved.
*/
public Bitmap getFrameAtTime(long timeUs, int option) {
@@ -255,7 +255,58 @@
throw new IllegalArgumentException("Unsupported option: " + option);
}
- return _getFrameAtTime(timeUs, option);
+ return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/);
+ }
+
+ /**
+ * Retrieve a video frame near a given timestamp scaled to a desired size.
+ * Call this method after setDataSource(). This method finds a representative
+ * frame close to the given time position by considering the given option
+ * if possible, and returns it as a bitmap with same aspect ratio as the source
+ * while scaling it so that it fits into the desired size of dst_width by dst_height.
+ * This is useful for generating a thumbnail for an input data source or just to
+ * obtain a scaled frame at the given time position.
+ *
+ * @param timeUs The time position in microseconds where the frame will be retrieved.
+ * When retrieving the frame at the given time position, there is no
+ * guarantee that the data source has a frame located at the position.
+ * When this happens, a frame nearby will be returned. If timeUs is
+ * negative, time position and option will ignored, and any frame
+ * that the implementation considers as representative may be returned.
+ *
+ * @param option a hint on how the frame is found. Use
+ * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp earlier than or the same as timeUs. Use
+ * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp later than or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp closest to or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+ * or may not be a sync frame but is closest to or the same as timeUs.
+ * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at timeUs.
+ *
+ * @param dst_width expected output bitmap width
+ * @param dst_height expected output bitmap height
+ * @return A Bitmap of size not larger than dst_width by dst_height containing a
+ * scaled video frame, which can be null, if such a frame cannot be retrieved.
+ * @throws IllegalArgumentException if passed in invalid option or width by height
+ * is less than or equal to 0.
+ */
+ public Bitmap getScaledFrameAtTime(
+ long timeUs, int option, int dst_width, int dst_height) {
+ if (option < OPTION_PREVIOUS_SYNC ||
+ option > OPTION_CLOSEST) {
+ throw new IllegalArgumentException("Unsupported option: " + option);
+ }
+ if (dst_width <= 0) {
+ throw new IllegalArgumentException("Invalid width: " + dst_width);
+ }
+ if (dst_height <= 0) {
+ throw new IllegalArgumentException("Invalid height: " + dst_height);
+ }
+
+ return _getFrameAtTime(timeUs, option, dst_width, dst_height);
}
/**
@@ -273,8 +324,8 @@
* negative, time position and option will ignored, and any frame
* that the implementation considers as representative may be returned.
*
- * @return A Bitmap containing a representative video frame, which
- * can be null, if such a frame cannot be retrieved.
+ * @return A Bitmap of size dst_widthxdst_height containing a representative
+ * video frame, which can be null, if such a frame cannot be retrieved.
*
* @see #getFrameAtTime(long, int)
*/
@@ -297,17 +348,16 @@
* @see #getFrameAtTime(long, int)
*/
public Bitmap getFrameAtTime() {
- return getFrameAtTime(-1, OPTION_CLOSEST_SYNC);
+ return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/);
}
- private native Bitmap _getFrameAtTime(long timeUs, int option);
+ private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
-
/**
* Call this method after setDataSource(). This method finds the optional
* graphic or album/cover art associated associated with the data source. If
* there are more than one pictures, (any) one of them is returned.
- *
+ *
* @return null if no such graphic is found.
*/
public byte[] getEmbeddedPicture() {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 849bd01..5a16c36 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -73,6 +73,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.net.CookieHandler;
+import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
@@ -1004,19 +1006,28 @@
/**
* Sets the data source as a content Uri.
*
- * @param context the Context to use when resolving the Uri
- * @param uri the Content URI of the data you want to play
- * @param headers the headers to be sent together with the request for the data
- * The headers must not include cookies. Instead, use the cookies param.
- * @param cookies the cookies to be sent together with the request
- * @throws IllegalStateException if it is called in an invalid state
- * @throws NullPointerException if context or uri is null
- * @throws IOException if uri has a file scheme and an I/O error occurs
+ * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
+ * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
+ * this API to pass the cookies as a list of HttpCookie. If the app has not installed
+ * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
+ * the provided cookies. If the app has installed its own handler already, this API requires the
+ * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
*
* <p><strong>Note</strong> that the cross domain redirection is allowed by default,
* but that can be changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
* disallow or allow cross domain redirection.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param headers the headers to be sent together with the request for the data
+ * The headers must not include cookies. Instead, use the cookies param.
+ * @param cookies the cookies to be sent together with the request
+ * @throws IllegalArgumentException if cookies are provided and the installed handler is not
+ * a CookieManager
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws NullPointerException if context or uri is null
+ * @throws IOException if uri has a file scheme and an I/O error occurs
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
@@ -1029,6 +1040,14 @@
throw new NullPointerException("uri param can not be null.");
}
+ if (cookies != null) {
+ CookieHandler cookieHandler = CookieHandler.getDefault();
+ if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
+ throw new IllegalArgumentException("The cookie handler has to be of CookieManager "
+ + "type when cookies are provided.");
+ }
+ }
+
// The context and URI usually belong to the calling user. Get a resolver for that user
// and strip out the userId from the URI if present.
final ContentResolver resolver = context.getContentResolver();
@@ -1064,15 +1083,15 @@
/**
* Sets the data source as a content Uri.
*
- * @param context the Context to use when resolving the Uri
- * @param uri the Content URI of the data you want to play
- * @param headers the headers to be sent together with the request for the data
- * @throws IllegalStateException if it is called in an invalid state
- *
* <p><strong>Note</strong> that the cross domain redirection is allowed by default,
* but that can be changed with key/value pairs through the headers parameter with
* "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
* disallow or allow cross domain redirection.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param headers the headers to be sent together with the request for the data
+ * @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers)
@@ -1093,15 +1112,15 @@
/**
* Sets the data source (file-path or http/rtsp URL) to use.
*
- * @param path the path of the file, or the http/rtsp URL of the stream you want to play
- * @throws IllegalStateException if it is called in an invalid state
- *
* <p>When <code>path</code> refers to a local file, the file may actually be opened by a
* process other than the calling application. This implies that the pathname
* should be an absolute path (as any other process runs with unspecified current working
* directory), and that the pathname should reference a world-readable file.
* As an alternative, the application could first open the file for reading,
* and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
+ *
+ * @param path the path of the file, or the http/rtsp URL of the stream you want to play
+ * @throws IllegalStateException if it is called in an invalid state
*/
public void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 9862564..2bccd88 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -256,7 +256,13 @@
*/
private void forceCloseConnection() {
if (mServiceConnection != null) {
- mContext.unbindService(mServiceConnection);
+ try {
+ mContext.unbindService(mServiceConnection);
+ } catch (IllegalArgumentException e) {
+ if (DBG) {
+ Log.d(TAG, "unbindService failed", e);
+ }
+ }
}
mState = CONNECT_STATE_DISCONNECTED;
mServiceConnection = null;
@@ -445,6 +451,9 @@
ResultReceiver receiver = new ResultReceiver(mHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (!isConnected()) {
+ return;
+ }
if (resultCode != 0 || resultData == null
|| !resultData.containsKey(MediaBrowserService.KEY_MEDIA_ITEM)) {
cb.onError(mediaId);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 07cfbda..df87e0f 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -367,7 +367,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
if (mTvInputSessionImpl == null) {
// The session has been finished.
finishInputEvent(event, false);
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index b52906d..4df645d 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -700,6 +700,13 @@
new Result<MediaBrowser.MediaItem>(itemId) {
@Override
void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
+ if (mConnections.get(connection.callbacks.asBinder()) != connection) {
+ if (DBG) {
+ Log.d(TAG, "Not sending onLoadItem result for connection that has"
+ + " been disconnected. pkg=" + connection.pkg + " id=" + itemId);
+ }
+ return;
+ }
if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) {
receiver.send(RESULT_ERROR, null);
return;
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 71f3856..4659ae1 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -244,9 +244,11 @@
}
}
-static jobject android_media_MediaMetadataRetriever_getFrameAtTime(JNIEnv *env, jobject thiz, jlong timeUs, jint option)
+static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
+ JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
{
- ALOGV("getFrameAtTime: %lld us option: %d", (long long)timeUs, option);
+ ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
+ (long long)timeUs, option, dst_width, dst_height);
MediaMetadataRetriever* retriever = getRetriever(env, thiz);
if (retriever == 0) {
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
@@ -274,15 +276,19 @@
fields.createConfigMethod,
GraphicsJNI::colorTypeToLegacyBitmapConfig(kRGB_565_SkColorType));
- uint32_t width, height;
+ uint32_t width, height, displayWidth, displayHeight;
bool swapWidthAndHeight = false;
if (videoFrame->mRotationAngle == 90 || videoFrame->mRotationAngle == 270) {
width = videoFrame->mHeight;
height = videoFrame->mWidth;
swapWidthAndHeight = true;
+ displayWidth = videoFrame->mDisplayHeight;
+ displayHeight = videoFrame->mDisplayWidth;
} else {
width = videoFrame->mWidth;
height = videoFrame->mHeight;
+ displayWidth = videoFrame->mDisplayWidth;
+ displayHeight = videoFrame->mDisplayHeight;
}
jobject jBitmap = env->CallStaticObjectMethod(
@@ -308,22 +314,26 @@
videoFrame->mHeight,
videoFrame->mRotationAngle);
- if (videoFrame->mDisplayWidth != videoFrame->mWidth ||
- videoFrame->mDisplayHeight != videoFrame->mHeight) {
- uint32_t displayWidth = videoFrame->mDisplayWidth;
- uint32_t displayHeight = videoFrame->mDisplayHeight;
- if (swapWidthAndHeight) {
- displayWidth = videoFrame->mDisplayHeight;
- displayHeight = videoFrame->mDisplayWidth;
- }
+ if (dst_width <= 0 || dst_height <= 0) {
+ dst_width = displayWidth;
+ dst_height = displayHeight;
+ } else {
+ float factor = std::min((float)dst_width / (float)displayWidth,
+ (float)dst_height / (float)displayHeight);
+ dst_width = std::round(displayWidth * factor);
+ dst_height = std::round(displayHeight * factor);
+ }
+
+ if ((uint32_t)dst_width != videoFrame->mWidth ||
+ (uint32_t)dst_height != videoFrame->mHeight) {
ALOGV("Bitmap dimension is scaled from %dx%d to %dx%d",
- width, height, displayWidth, displayHeight);
+ width, height, dst_width, dst_height);
jobject scaledBitmap = env->CallStaticObjectMethod(fields.bitmapClazz,
- fields.createScaledBitmapMethod,
- jBitmap,
- displayWidth,
- displayHeight,
- true);
+ fields.createScaledBitmapMethod,
+ jBitmap,
+ dst_width,
+ dst_height,
+ true);
return scaledBitmap;
}
@@ -474,7 +484,7 @@
{"setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
- {"_getFrameAtTime", "(JI)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
+ {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
{"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
{"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
{"release", "()V", (void *)android_media_MediaMetadataRetriever_release},
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 9b5982b9..f243b51 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -34,6 +34,7 @@
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.provider.DocumentsContract;
+import android.provider.MetadataReader;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -900,6 +901,9 @@
protectionState == MtpConstants.PROTECTION_STATUS_NONE) {
flag |= Document.FLAG_DIR_SUPPORTS_CREATE;
}
+ if (MetadataReader.isSupportedMimeType(mimeType)) {
+ flag |= Document.FLAG_SUPPORTS_METADATA;
+ }
if (thumbnailSize > 0) {
flag |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index eb2d8aa..8c8116b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -16,6 +16,7 @@
package com.android.mtp;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -37,11 +38,12 @@
import android.os.ParcelFileDescriptor;
import android.os.ProxyFileDescriptorCallback;
import android.os.storage.StorageManager;
+import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Path;
import android.provider.DocumentsContract.Root;
-import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
+import android.provider.MetadataReader;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
@@ -50,14 +52,16 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import libcore.io.IoUtils;
+
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
-import libcore.io.IoUtils;
/**
* DocumentsProvider for MTP devices.
@@ -107,7 +111,7 @@
mResources = getContext().getResources();
mMtpManager = new MtpManager(getContext());
mResolver = getContext().getContentResolver();
- mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
+ mDeviceToolkits = new HashMap<>();
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mIntentSender = new ServiceIntentSender(getContext());
@@ -151,7 +155,7 @@
mResources = resources;
mMtpManager = mtpManager;
mResolver = resolver;
- mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
+ mDeviceToolkits = new HashMap<>();
mDatabase = database;
mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase);
mIntentSender = intentSender;
@@ -548,6 +552,29 @@
}
}
+ @Override
+ public @Nullable Bundle getDocumentMetadata(String docId) throws FileNotFoundException {
+ String mimeType = getDocumentType(docId);
+
+ if (!MetadataReader.isSupportedMimeType(mimeType)) {
+ return null;
+ }
+
+ InputStream stream = null;
+ try {
+ stream = new ParcelFileDescriptor.AutoCloseInputStream(
+ openDocument(docId, "r", null));
+ Bundle metadata = new Bundle();
+ MetadataReader.getMetadata(metadata, stream, mimeType, null);
+ return metadata;
+ } catch (IOException e) {
+ Log.e(TAG, "An error occurred retrieving the metadata", e);
+ return null;
+ } finally {
+ IoUtils.closeQuietly(stream);
+ }
+ }
+
void openDevice(int deviceId) throws IOException {
synchronized (mDeviceListLock) {
if (mDeviceToolkits.containsKey(deviceId)) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 7fe6cb9..32b169e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -237,7 +237,8 @@
assertEquals(
COLUMN_FLAGS,
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
- DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
+ DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
+ DocumentsContract.Document.FLAG_SUPPORTS_METADATA,
cursor.getInt(9));
assertEquals(2 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
assertEquals(
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 3fa5eb5..65c86df 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -329,7 +329,8 @@
assertEquals(
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
- DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
+ DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL |
+ DocumentsContract.Document.FLAG_SUPPORTS_METADATA,
cursor.getInt(4));
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
}
@@ -474,7 +475,8 @@
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(0, cursor.getLong(3));
- assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
+ assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL
+ | Document.FLAG_SUPPORTS_METADATA, cursor.getInt(4));
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
cursor.close();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index d07da93..7d4bc83 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -97,7 +97,6 @@
// Pairing broadcasts
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
- addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
// Fine-grained state broadcasts
addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
@@ -344,24 +343,6 @@
}
}
- private class PairingCancelHandler implements Handler {
- public void onReceive(Context context, Intent intent, BluetoothDevice device) {
- if (device == null) {
- Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
- return;
- }
- CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
- if (cachedDevice == null) {
- Log.e(TAG, "ACTION_PAIRING_CANCEL with no cached device");
- return;
- }
- int errorMsg = R.string.bluetooth_pairing_error_message;
- if (context != null && cachedDevice != null) {
- Utils.showError(context, cachedDevice.getName(), errorMsg);
- }
- }
- }
-
private class DockEventHandler implements Handler {
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
// Remove if unpair device upon undocking
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 9620a91..fc0a444 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -149,13 +149,6 @@
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
/**
- * @deprecated Use {@link #META_DATA_PREFERENCE_TITLE} with {@code android:resource}
- */
- @Deprecated
- public static final String META_DATA_PREFERENCE_TITLE_RES_ID =
- "com.android.settings.title.resid";
-
- /**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the summary text that should be displayed for the preference.
*/
@@ -176,7 +169,7 @@
* custom view which should be displayed for the preference. The custom view will be inflated
* as a remote view.
*
- * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id
+ * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id
* of the summary TextView to '@android:id/summary'.
*/
public static final String META_DATA_PREFERENCE_CUSTOM_VIEW =
@@ -411,14 +404,7 @@
metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE);
}
}
- int resId = 0;
- if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) {
- resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID);
- if (resId != 0) {
- title = res.getString(resId);
- }
- }
- if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
} else {
@@ -464,12 +450,14 @@
}
// Set the icon
- if (iconFromUri != null) {
- tile.icon = Icon.createWithResource(iconFromUri.first, iconFromUri.second);
- } else {
- if (icon == 0) {
+ if (icon == 0) {
+ // Only fallback to activityinfo.icon if metadata does not contain ICON_URI.
+ // ICON_URI should be loaded in app UI when need the icon object.
+ if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
icon = activityInfo.icon;
}
+ }
+ if (icon != 0) {
tile.icon = Icon.createWithResource(activityInfo.packageName, icon);
}
@@ -492,7 +480,7 @@
/**
* Gets the icon package name and resource id from content provider.
- * @param Context context
+ * @param context context
* @param packageName package name of the target activity
* @param uriString URI for the content provider
* @param providerMap Maps URI authorities to providers
@@ -522,7 +510,7 @@
/**
* Gets text associated with the input key from the content provider.
- * @param Context context
+ * @param context context
* @param uriString URI for the content provider
* @param providerMap Maps URI authorities to providers
* @param key Key mapping to the text in bundle returned by the content provider
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
index 6eeb593..88adcdb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java
@@ -18,10 +18,14 @@
import android.os.Handler;
import android.os.Looper;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
public class ThreadUtils {
private static volatile Thread sMainThread;
private static volatile Handler sMainThreadHandler;
+ private static volatile ExecutorService sSingleThreadExecutor;
/**
* Returns true if the current thread is the UI thread.
@@ -54,6 +58,16 @@
}
/**
+ * Posts runnable in background using shared background thread pool.
+ */
+ public static void postOnBackgroundThread(Runnable runnable) {
+ if (sSingleThreadExecutor == null) {
+ sSingleThreadExecutor = Executors.newSingleThreadExecutor();
+ }
+ sSingleThreadExecutor.execute(runnable);
+ }
+
+ /**
* Posts the runnable on the main thread.
*/
public static void postOnMainThread(Runnable runnable) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index bffd6cc..33af4039 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -37,6 +37,7 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkScoreCache;
@@ -140,6 +141,9 @@
static final String KEY_CONFIG = "key_config";
static final String KEY_FQDN = "key_fqdn";
static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name";
+ static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap";
+ static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type";
+ static final String KEY_CARRIER_NAME = "key_carrier_name";
static final AtomicInteger sLastId = new AtomicInteger(0);
/**
@@ -197,6 +201,13 @@
private String mFqdn;
private String mProviderFriendlyName;
+ private boolean mIsCarrierAp = false;
+ /**
+ * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP.
+ */
+ private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE;
+ private String mCarrierName = null;
+
public AccessPoint(Context context, Bundle savedState) {
mContext = context;
mConfig = savedState.getParcelable(KEY_CONFIG);
@@ -233,6 +244,15 @@
if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) {
mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME);
}
+ if (savedState.containsKey(KEY_IS_CARRIER_AP)) {
+ mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP);
+ }
+ if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) {
+ mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE);
+ }
+ if (savedState.containsKey(KEY_CARRIER_NAME)) {
+ mCarrierName = savedState.getString(KEY_CARRIER_NAME);
+ }
update(mConfig, mInfo, mNetworkInfo);
updateRssi();
updateSeen();
@@ -291,6 +311,9 @@
this.mId = that.mId;
this.mSpeed = that.mSpeed;
this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered;
+ this.mIsCarrierAp = that.mIsCarrierAp;
+ this.mCarrierApEapType = that.mCarrierApEapType;
+ this.mCarrierName = that.mCarrierName;
}
/**
@@ -670,6 +693,18 @@
return null;
}
+ public boolean isCarrierAp() {
+ return mIsCarrierAp;
+ }
+
+ public int getCarrierApEapType() {
+ return mCarrierApEapType;
+ }
+
+ public String getCarrierName() {
+ return mCarrierName;
+ }
+
public String getSavedNetworkSummary() {
WifiConfiguration config = mConfig;
if (config != null) {
@@ -712,6 +747,9 @@
// This is the active connection on passpoint
summary.append(getSummary(mContext, getDetailedState(),
false, config.providerFriendlyName));
+ } else if (isActive() && config != null && getDetailedState() == DetailedState.CONNECTED
+ && mIsCarrierAp) {
+ summary.append(String.format(mContext.getString(R.string.connected_via_carrier), mCarrierName));
} else if (isActive()) {
// This is the active connection on non-passpoint network
summary.append(getSummary(mContext, getDetailedState(),
@@ -745,6 +783,8 @@
}
} else if (config != null && config.getNetworkSelectionStatus().isNotRecommended()) {
summary.append(mContext.getString(R.string.wifi_disabled_by_recommendation_provider));
+ } else if (mIsCarrierAp) {
+ summary.append(String.format(mContext.getString(R.string.available_via_carrier), mCarrierName));
} else if (!isReachable()) { // Wifi out of range
summary.append(mContext.getString(R.string.wifi_not_in_range));
} else { // In range, not disabled.
@@ -1024,6 +1064,9 @@
mScanResultCache.put(result.BSSID, result);
updateRssi();
mSeen = result.timestamp; // even if the timestamp is old it is still valid
+ mIsCarrierAp = result.isCarrierAp;
+ mCarrierApEapType = result.carrierApEapType;
+ mCarrierName = result.carrierName;
}
public void saveWifiState(Bundle savedState) {
@@ -1045,6 +1088,9 @@
if (mProviderFriendlyName != null) {
savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName);
}
+ savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp);
+ savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType);
+ savedState.putString(KEY_CARRIER_NAME, mCarrierName);
}
public void setListener(AccessPointListener listener) {
@@ -1073,6 +1119,12 @@
mAccessPointListener.onAccessPointChanged(this);
}
+ // The carrier info in the ScanResult is set by the platform based on the SSID and will
+ // always be the same for all matching scan results.
+ mIsCarrierAp = result.isCarrierAp;
+ mCarrierApEapType = result.carrierApEapType;
+ mCarrierName = result.carrierName;
+
return true;
}
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 731a607..93bf3c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -53,6 +53,8 @@
private int mSecurity = AccessPoint.SECURITY_NONE;
private WifiConfiguration mWifiConfig;
private WifiInfo mWifiInfo;
+ private boolean mIsCarrierAp = false;
+ private String mCarrierName = null;
Context mContext;
private ArrayList<ScanResult> mScanResultCache;
@@ -85,6 +87,10 @@
}
bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity);
bundle.putInt(AccessPoint.KEY_SPEED, mSpeed);
+ bundle.putBoolean(AccessPoint.KEY_IS_CARRIER_AP, mIsCarrierAp);
+ if (mCarrierName != null) {
+ bundle.putString(AccessPoint.KEY_CARRIER_NAME, mCarrierName);
+ }
AccessPoint ap = new AccessPoint(mContext, bundle);
ap.setRssi(mRssi);
@@ -222,4 +228,14 @@
mScanResultCache = scanResultCache;
return this;
}
+
+ public TestAccessPointBuilder setIsCarrierAp(boolean isCarrierAp) {
+ mIsCarrierAp = isCarrierAp;
+ return this;
+ }
+
+ public TestAccessPointBuilder setCarrierName(String carrierName) {
+ mCarrierName = carrierName;
+ return this;
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 9645c94..24f0c7a 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -35,6 +35,7 @@
import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiNetworkScoreCache;
import android.net.wifi.WifiSsid;
@@ -491,6 +492,75 @@
R.string.wifi_check_password_try_again));
}
+ @Test
+ public void testSummaryString_showsAvaiableViaCarrier() {
+ String carrierName = "Test Carrier";
+ ScanResult result = new ScanResult();
+ result.BSSID = "00:11:22:33:44:55";
+ result.capabilities = "EAP";
+ result.isCarrierAp = true;
+ result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
+ result.carrierName = carrierName;
+
+ AccessPoint ap = new AccessPoint(mContext, result);
+ assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
+ R.string.available_via_carrier), carrierName));
+ assertThat(ap.isCarrierAp()).isEqualTo(true);
+ assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.SIM);
+ assertThat(ap.getCarrierName()).isEqualTo(carrierName);
+ }
+
+ @Test
+ public void testSummaryString_showsConnectedViaCarrier() {
+ int networkId = 123;
+ int rssi = -55;
+ String carrierName = "Test Carrier";
+ WifiConfiguration config = new WifiConfiguration();
+ config.networkId = networkId;
+ WifiInfo wifiInfo = new WifiInfo();
+ wifiInfo.setNetworkId(networkId);
+ wifiInfo.setRssi(rssi);
+
+ NetworkInfo networkInfo =
+ new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
+ networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", "");
+
+ AccessPoint ap = new TestAccessPointBuilder(mContext)
+ .setNetworkInfo(networkInfo)
+ .setNetworkId(networkId)
+ .setRssi(rssi)
+ .setWifiInfo(wifiInfo)
+ .setIsCarrierAp(true)
+ .setCarrierName(carrierName)
+ .build();
+ assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString(
+ R.string.connected_via_carrier), carrierName));
+ }
+
+ @Test
+ public void testUpdateScanResultWithCarrierInfo() {
+ String ssid = "ssid";
+ AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build();
+ assertThat(ap.isCarrierAp()).isEqualTo(false);
+ assertThat(ap.getCarrierApEapType()).isEqualTo(WifiEnterpriseConfig.Eap.NONE);
+ assertThat(ap.getCarrierName()).isEqualTo(null);
+
+ int carrierApEapType = WifiEnterpriseConfig.Eap.SIM;
+ String carrierName = "Test Carrier";
+ ScanResult scanResult = new ScanResult();
+ scanResult.SSID = ssid;
+ scanResult.BSSID = "00:11:22:33:44:55";
+ scanResult.capabilities = "";
+ scanResult.isCarrierAp = true;
+ scanResult.carrierApEapType = carrierApEapType;
+ scanResult.carrierName = carrierName;
+ assertThat(ap.update(scanResult)).isTrue();
+
+ assertThat(ap.isCarrierAp()).isEqualTo(true);
+ assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType);
+ assertThat(ap.getCarrierName()).isEqualTo(carrierName);
+ }
+
private ScoredNetwork buildScoredNetworkWithMockBadgeCurve() {
Bundle attr1 = new Bundle();
attr1.putParcelable(ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE, mockBadgeCurve);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index e9ca753..3e90435 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -535,14 +535,10 @@
info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri);
}
if (titleResId != 0) {
- info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
+ info.activityInfo.metaData.putInt(TileUtils.META_DATA_PREFERENCE_TITLE, titleResId);
} else if (title != null) {
info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
}
- if (titleResId != 0) {
- info.activityInfo.metaData.putInt(
- TileUtils.META_DATA_PREFERENCE_TITLE_RES_ID, titleResId);
- }
info.activityInfo.applicationInfo = new ApplicationInfo();
if (systemApp) {
info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 96f51c1..3d0147d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.BackupUtils;
import android.util.Log;
@@ -62,6 +63,9 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_BACKUP = DEBUG || false;
+ private static final byte[] NULL_VALUE = new byte[0];
+ private static final int NULL_SIZE = -1;
+
private static final String KEY_SYSTEM = "system";
private static final String KEY_SECURE = "secure";
private static final String KEY_GLOBAL = "global";
@@ -608,7 +612,7 @@
// Restore only the white list data.
int pos = 0;
- Map<String, String> cachedEntries = new HashMap<String, String>();
+ final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
ContentValues contentValues = new ContentValues(2);
SettingsHelper settingsHelper = mSettingsHelper;
ContentResolver cr = getContentResolver();
@@ -616,28 +620,36 @@
final int whiteListSize = whitelist.length;
for (int i = 0; i < whiteListSize; i++) {
String key = whitelist[i];
- String value = cachedEntries.remove(key);
- // If the value not cached, let us look it up.
- if (value == null) {
+ String value = null;
+ boolean hasValueToRestore = false;
+ if (cachedEntries.indexOfKey(key) >= 0) {
+ value = cachedEntries.remove(key);
+ hasValueToRestore = true;
+ } else {
+ // If the value not cached, let us look it up.
while (pos < bytes) {
int length = readInt(settings, pos);
pos += INTEGER_BYTE_COUNT;
- String dataKey = length > 0 ? new String(settings, pos, length) : null;
+ String dataKey = length >= 0 ? new String(settings, pos, length) : null;
pos += length;
length = readInt(settings, pos);
pos += INTEGER_BYTE_COUNT;
- String dataValue = length > 0 ? new String(settings, pos, length) : null;
- pos += length;
+ String dataValue = null;
+ if (length >= 0) {
+ dataValue = new String(settings, pos, length);
+ pos += length;
+ }
if (key.equals(dataKey)) {
value = dataValue;
+ hasValueToRestore = true;
break;
}
cachedEntries.put(dataKey, dataValue);
}
}
- if (value == null) {
+ if (!hasValueToRestore) {
continue;
}
@@ -724,50 +736,56 @@
* @return The byte array of extracted values.
*/
private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
- final int settingsCount = settings.length;
- byte[][] values = new byte[settingsCount * 2][]; // keys and values
if (!cursor.moveToFirst()) {
Log.e(TAG, "Couldn't read from the cursor");
return new byte[0];
}
+ final int nameColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
+ final int valueColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+
// Obtain the relevant data in a temporary array.
int totalSize = 0;
int backedUpSettingIndex = 0;
- Map<String, String> cachedEntries = new HashMap<String, String>();
+ final int settingsCount = settings.length;
+ final byte[][] values = new byte[settingsCount * 2][]; // keys and values
+ final ArrayMap<String, String> cachedEntries = new ArrayMap<>();
for (int i = 0; i < settingsCount; i++) {
- String key = settings[i];
- String value = cachedEntries.remove(key);
-
- final int nameColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.NAME);
- final int valueColumnIndex = cursor.getColumnIndex(Settings.NameValueTable.VALUE);
+ final String key = settings[i];
// If the value not cached, let us look it up.
- if (value == null) {
+ String value = null;
+ boolean hasValueToBackup = false;
+ if (cachedEntries.indexOfKey(key) >= 0) {
+ value = cachedEntries.remove(key);
+ hasValueToBackup = true;
+ } else {
while (!cursor.isAfterLast()) {
- String cursorKey = cursor.getString(nameColumnIndex);
- String cursorValue = cursor.getString(valueColumnIndex);
+ final String cursorKey = cursor.getString(nameColumnIndex);
+ final String cursorValue = cursor.getString(valueColumnIndex);
cursor.moveToNext();
if (key.equals(cursorKey)) {
value = cursorValue;
+ hasValueToBackup = true;
break;
}
cachedEntries.put(cursorKey, cursorValue);
}
}
+ if (!hasValueToBackup) {
+ continue;
+ }
+
// Intercept the keys and see if they need special handling
value = mSettingsHelper.onBackupValue(key, value);
- if (value == null) {
- continue;
- }
// Write the key and value in the intermediary array.
- byte[] keyBytes = key.getBytes();
+ final byte[] keyBytes = key.getBytes();
totalSize += INTEGER_BYTE_COUNT + keyBytes.length;
values[backedUpSettingIndex * 2] = keyBytes;
- byte[] valueBytes = value.getBytes();
+ final byte[] valueBytes = (value != null) ? value.getBytes() : NULL_VALUE;
totalSize += INTEGER_BYTE_COUNT + valueBytes.length;
values[backedUpSettingIndex * 2 + 1] = valueBytes;
@@ -783,8 +801,13 @@
int pos = 0;
final int keyValuePairCount = backedUpSettingIndex * 2;
for (int i = 0; i < keyValuePairCount; i++) {
- pos = writeInt(result, pos, values[i].length);
- pos = writeBytes(result, pos, values[i]);
+ final byte[] value = values[i];
+ if (value != NULL_VALUE) {
+ pos = writeInt(result, pos, value.length);
+ pos = writeBytes(result, pos, value);
+ } else {
+ pos = writeInt(result, pos, NULL_SIZE);
+ }
}
return result;
}
diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
index 27caf96..e38b482 100644
--- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml
@@ -8,5 +8,5 @@
-->
<path
android:pathData="M17.32,5.06h-2.91V3.6c0,-0.81 -0.65,-1.46 -1.46,-1.46h-2.91c-0.81,0 -1.46,0.65 -1.46,1.46v1.46H5.68c-0.81,0 -1.45,0.65 -1.45,1.46l-0.01,8c0,0.81 0.65,1.46 1.46,1.46h11.64c0.81,0 1.46,-0.65 1.46,-1.46v-8C18.78,5.7 18.13,5.06 17.32,5.06zM11.5,11.6c-0.8,0 -1.46,-0.65 -1.46,-1.46c0,-0.8 0.65,-1.46 1.46,-1.46s1.46,0.65 1.46,1.46C12.96,10.95 12.3,11.6 11.5,11.6zM12.96,5.06h-2.91V3.6h2.91V5.06z"
- android:fillColor="#FFFFFF"/>
-</vector>
\ No newline at end of file
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 054d520..f72f379 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -116,11 +116,11 @@
<color name="segmented_buttons_background">#14FFFFFF</color><!-- 8% white -->
<color name="dark_mode_icon_color_single_tone">#99000000</color>
- <color name="dark_mode_icon_color_dual_tone_background">#4d000000</color>
+ <color name="dark_mode_icon_color_dual_tone_background">#3d000000</color>
<color name="dark_mode_icon_color_dual_tone_fill">#7a000000</color>
<color name="light_mode_icon_color_single_tone">#ffffff</color>
- <color name="light_mode_icon_color_dual_tone_background">#5dffffff</color>
+ <color name="light_mode_icon_color_dual_tone_background">#4dffffff</color>
<color name="light_mode_icon_color_dual_tone_fill">#ffffff</color>
<color name="volume_settings_icon_color">#7fffffff</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5917dc5..506c294 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -277,6 +277,18 @@
<item>28</item> <!-- 4: SUN -->
</integer-array>
+ <!-- Doze: Table that translates sensor values from the doze_brightness_sensor_type sensor
+ to an opacity value for a black scrim that is overlayed in AOD1.
+ Valid range is from 0 (transparent) to 255 (opaque).
+ -1 means keeping the current opacity. -->
+ <integer-array name="config_doze_brightness_sensor_to_scrim_opacity">
+ <item>-1</item> <!-- 0: OFF -->
+ <item>0</item> <!-- 1: NIGHT -->
+ <item>0</item> <!-- 2: LOW -->
+ <item>0</item> <!-- 3: HIGH -->
+ <item>0</item> <!-- 4: SUN -->
+ </integer-array>
+
<!-- Doze: whether the double tap sensor reports 2D touch coordinates -->
<bool name="doze_double_tap_reports_touch_coordinates">false</bool>
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e5c729f..9c1cb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -16,6 +16,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.hardware.SensorManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -84,6 +85,7 @@
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerServiceImpl;
+import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
@@ -156,6 +158,9 @@
mProviders.put(ActivityStarterDelegate.class, () ->
getDependency(ActivityStarter.class));
+ mProviders.put(AsyncSensorManager.class, () ->
+ new AsyncSensorManager(mContext.getSystemService(SensorManager.class)));
+
mProviders.put(BluetoothController.class, () ->
new BluetoothControllerImpl(mContext, getDependency(BG_LOOPER)));
diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
index 40ea4ec..37e2402 100644
--- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
+++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
@@ -119,7 +119,7 @@
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
- | WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
+ | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
lp.setTitle("RoundedOverlay");
lp.gravity = Gravity.TOP;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
index e92ed2f..e4b405f 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManager.java
@@ -35,6 +35,7 @@
import com.android.systemui.UiOffloadThread;
import com.android.systemui.analytics.DataCollector;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.util.AsyncSensorManager;
import java.io.PrintWriter;
@@ -87,7 +88,7 @@
private FalsingManager(Context context) {
mContext = context;
- mSensorManager = mContext.getSystemService(SensorManager.class);
+ mSensorManager = Dependency.get(AsyncSensorManager.class);
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mDataCollector = DataCollector.getInstance(mContext);
mHumanInteractionClassifier = HumanInteractionClassifier.getInstance(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 9ba7be8..34c05a5 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -21,6 +21,8 @@
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.Trace;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
import android.view.IWallpaperVisibilityListener;
@@ -31,6 +33,7 @@
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.types.ExtractionType;
import com.android.internal.colorextraction.types.Tonal;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import java.io.FileDescriptor;
@@ -75,6 +78,14 @@
Log.w(TAG, "Can't listen to wallpaper visibility changes", e);
}
}
+
+ WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
+ if (wallpaperManager != null) {
+ // Listen to all users instead of only the current one.
+ wallpaperManager.removeOnColorsChangedListener(this);
+ wallpaperManager.addOnColorsChangedListener(this, null /* handler */,
+ UserHandle.USER_ALL);
+ }
}
private void updateDefaultGradients(WallpaperColors colors) {
@@ -82,7 +93,12 @@
}
@Override
- public void onColorsChanged(WallpaperColors colors, int which) {
+ public void onColorsChanged(WallpaperColors colors, int which, int userId) {
+ if (userId != KeyguardUpdateMonitor.getCurrentUser()) {
+ // Colors do not belong to current user, ignoring.
+ return;
+ }
+
super.onColorsChanged(colors, which);
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 91ca571..302bc2d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -24,10 +24,12 @@
import android.os.Handler;
import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.AsyncSensorManager;
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
@@ -39,7 +41,7 @@
/** Creates a DozeMachine with its parts for {@code dozeService}. */
public DozeMachine assembleMachine(DozeService dozeService) {
Context context = dozeService;
- SensorManager sensorManager = context.getSystemService(SensorManager.class);
+ SensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
DozeHost host = getHost(dozeService);
@@ -63,7 +65,7 @@
handler, wakeLock, machine),
createDozeUi(context, host, wakeLock, machine, handler, alarmManager),
createDozeScreenState(wrappedService),
- createDozeScreenBrightness(context, wrappedService, sensorManager, handler),
+ createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler),
});
return machine;
@@ -74,10 +76,11 @@
}
private DozeMachine.Part createDozeScreenBrightness(Context context,
- DozeMachine.Service service, SensorManager sensorManager, Handler handler) {
+ DozeMachine.Service service, SensorManager sensorManager, DozeHost host,
+ Handler handler) {
Sensor sensor = DozeSensors.findSensorWithType(sensorManager,
context.getString(R.string.doze_brightness_sensor_type));
- return new DozeScreenBrightness(context, service, sensorManager, sensor, handler);
+ return new DozeScreenBrightness(context, service, sensorManager, sensor, host, handler);
}
private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 9b97634..7db118d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -42,6 +42,7 @@
void onDoubleTap(float x, float y);
+ default void setAodDimmingScrim(float scrimOpacity) {}
void setDozeScreenBrightness(int value);
void onIgnoreTouchWhilePulsing(boolean ignore);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 32baf94..30420529 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -32,22 +32,28 @@
public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListener {
private final Context mContext;
private final DozeMachine.Service mDozeService;
+ private final DozeHost mDozeHost;
private final Handler mHandler;
private final SensorManager mSensorManager;
private final Sensor mLightSensor;
private final int[] mSensorToBrightness;
+ private final int[] mSensorToScrimOpacity;
private boolean mRegistered;
public DozeScreenBrightness(Context context, DozeMachine.Service service,
- SensorManager sensorManager, Sensor lightSensor, Handler handler) {
+ SensorManager sensorManager, Sensor lightSensor, DozeHost host,
+ Handler handler) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
mLightSensor = lightSensor;
+ mDozeHost = host;
mHandler = handler;
mSensorToBrightness = context.getResources().getIntArray(
R.array.config_doze_brightness_sensor_to_brightness);
+ mSensorToScrimOpacity = context.getResources().getIntArray(
+ R.array.config_doze_brightness_sensor_to_scrim_opacity);
}
@Override
@@ -74,13 +80,26 @@
@Override
public void onSensorChanged(SensorEvent event) {
if (mRegistered) {
- int brightness = computeBrightness((int) event.values[0]);
+ int sensorValue = (int) event.values[0];
+ int brightness = computeBrightness(sensorValue);
if (brightness > 0) {
mDozeService.setDozeScreenBrightness(brightness);
}
+
+ int scrimOpacity = computeScrimOpacity(sensorValue);
+ if (scrimOpacity >= 0) {
+ mDozeHost.setAodDimmingScrim(scrimOpacity / 255f);
+ }
}
}
+ private int computeScrimOpacity(int sensorValue) {
+ if (sensorValue < 0 || sensorValue >= mSensorToScrimOpacity.length) {
+ return -1;
+ }
+ return mSensorToScrimOpacity[sensorValue];
+ }
+
private int computeBrightness(int sensorValue) {
if (sensorValue < 0 || sensorValue >= mSensorToBrightness.length) {
return -1;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index bf1c060..566353c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -87,25 +87,29 @@
mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
null /* setting */,
dozeParameters.getPulseOnSigMotion(),
- DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */),
+ DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
+ false /* touchscreen */),
mPickupSensor = new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
Settings.Secure.DOZE_PULSE_ON_PICK_UP,
config.pulseOnPickupAvailable(),
- DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */),
+ DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */,
+ false /* touchscreen */),
new TriggerSensor(
findSensorWithType(config.doubleTapSensorType()),
Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
true /* configured */,
DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
- dozeParameters.doubleTapReportsTouchCoordinates()),
+ dozeParameters.doubleTapReportsTouchCoordinates(),
+ true /* touchscreen */),
new TriggerSensor(
findSensorWithType(config.longPressSensorType()),
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
false /* settingDef */,
true /* configured */,
DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
- true /* reports touch coordinates */),
+ true /* reports touch coordinates */,
+ true /* touchscreen */),
};
mProxSensor = new ProxSensor();
@@ -141,6 +145,15 @@
}
}
+ /** Set the listening state of only the sensors that require the touchscreen. */
+ public void setTouchscreenSensorsListening(boolean listening) {
+ for (TriggerSensor sensor : mSensors) {
+ if (sensor.mRequiresTouchscreen) {
+ sensor.setListening(listening);
+ }
+ }
+ }
+
public void reregisterAllSensors() {
for (TriggerSensor s : mSensors) {
s.setListening(false);
@@ -278,25 +291,28 @@
final String mSetting;
final boolean mReportsTouchCoordinates;
final boolean mSettingDefault;
+ final boolean mRequiresTouchscreen;
private boolean mRequested;
private boolean mRegistered;
private boolean mDisabled;
public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
- boolean reportsTouchCoordinates) {
+ boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
this(sensor, setting, true /* settingDef */, configured, pulseReason,
- reportsTouchCoordinates);
+ reportsTouchCoordinates, requiresTouchscreen);
}
public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
- boolean configured, int pulseReason, boolean reportsTouchCoordinates) {
+ boolean configured, int pulseReason, boolean reportsTouchCoordinates,
+ boolean requiresTouchscreen) {
mSensor = sensor;
mSetting = setting;
mSettingDefault = settingDef;
mConfigured = configured;
mPulseReason = pulseReason;
mReportsTouchCoordinates = reportsTouchCoordinates;
+ mRequiresTouchscreen = requiresTouchscreen;
}
public void setListening(boolean listen) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 15981e5..4583160 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -198,6 +198,7 @@
mDozeSensors.setListening(false);
break;
case DOZE_PULSING:
+ mDozeSensors.setTouchscreenSensorsListening(false);
mDozeSensors.setProxListening(true);
break;
case FINISH:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 8847452..dc626fb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -95,17 +95,22 @@
unscheduleTimeTick();
break;
}
- mHost.setAnimateWakeup(shouldAnimateWakeup(newState));
+ updateAnimateWakeup(newState);
}
- private boolean shouldAnimateWakeup(DozeMachine.State state) {
+ private void updateAnimateWakeup(DozeMachine.State state) {
switch (state) {
case DOZE_REQUEST_PULSE:
case DOZE_PULSING:
case DOZE_PULSE_DONE:
- return true;
+ mHost.setAnimateWakeup(true);
+ break;
+ case FINISH:
+ // Keep current state.
+ break;
default:
- return false;
+ mHost.setAnimateWakeup(false);
+ break;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
index 6733421..abc5667 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
@@ -65,7 +65,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
boolean handled = true;
try {
// To be implemented for input handling over Pip windows
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 2283c13..3794ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -56,6 +56,8 @@
private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
+ private float mAodFrontScrimOpacity = 0;
+
public DozeScrimController(ScrimController scrimController, Context context) {
mContext = context;
mScrimController = scrimController;
@@ -70,7 +72,8 @@
mDozingAborted = false;
abortAnimations();
mScrimController.setDozeBehindAlpha(1f);
- mScrimController.setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? 0f : 1f);
+ mScrimController.setDozeInFrontAlpha(
+ mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
} else {
cancelPulsing();
if (animate) {
@@ -88,6 +91,19 @@
}
}
+ /**
+ * Set the opacity of the front scrim when showing AOD1
+ *
+ * Used to emulate lower brightness values than the hardware supports natively.
+ */
+ public void setAodDimmingScrim(float scrimOpacity) {
+ mAodFrontScrimOpacity = scrimOpacity;
+ if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
+ && mDozeParameters.getAlwaysOn()) {
+ mScrimController.setDozeInFrontAlpha(mAodFrontScrimOpacity);
+ }
+ }
+
public void setWakeAndUnlocking() {
// Immediately abort the doze scrims in case of wake-and-unlock
// for pulsing so the Keyguard fade-out animation scrim can take over.
@@ -126,7 +142,8 @@
if (mDozing && !mWakeAndUnlocking) {
mScrimController.setDozeBehindAlpha(1f);
mScrimController.setDozeInFrontAlpha(
- mDozeParameters.getAlwaysOn() && !mDozingAborted ? 0f : 1f);
+ mDozeParameters.getAlwaysOn() && !mDozingAborted ?
+ mAodFrontScrimOpacity : 1f);
}
}
@@ -337,7 +354,7 @@
// Signal that the pulse is all finished so we can turn the screen off now.
pulseFinished();
if (mDozeParameters.getAlwaysOn()) {
- mScrimController.setDozeInFrontAlpha(0);
+ mScrimController.setDozeInFrontAlpha(mAodFrontScrimOpacity);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 02202cf..cb96dea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -29,6 +29,7 @@
import com.android.keyguard.LatencyTracker;
import com.android.systemui.Dependency;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
/**
@@ -101,6 +102,7 @@
private final Context mContext;
private int mPendingAuthenticatedUserId = -1;
private boolean mPendingShowBouncer;
+ private boolean mHasScreenTurnedOnSinceAuthenticating;
public FingerprintUnlockController(Context context,
DozeScrimController dozeScrimController,
@@ -113,6 +115,7 @@
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
mUpdateMonitor.registerCallback(this);
Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
+ Dependency.get(ScreenLifecycle.class).addObserver(mScreenObserver);
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
mDozeScrimController = dozeScrimController;
mKeyguardViewMediator = keyguardViewMediator;
@@ -184,8 +187,13 @@
Trace.endSection();
return;
}
+ startWakeAndUnlock(calculateMode());
+ }
+
+ public void startWakeAndUnlock(int mode) {
boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive();
- mMode = calculateMode();
+ mMode = mode;
+ mHasScreenTurnedOnSinceAuthenticating = false;
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING && pulsingOrAod()) {
// If we are waking the device up while we are pulsing the clock and the
// notifications would light up first, creating an unpleasant animation.
@@ -228,6 +236,7 @@
true /* allowEnterAnimation */);
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
+
mDozeScrimController.abortDoze();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -354,4 +363,16 @@
}
}
};
+
+ private final ScreenLifecycle.Observer mScreenObserver =
+ new ScreenLifecycle.Observer() {
+ @Override
+ public void onScreenTurnedOn() {
+ mHasScreenTurnedOnSinceAuthenticating = true;
+ }
+ };
+
+ public boolean hasScreenTurnedOnSinceAuthenticating() {
+ return mHasScreenTurnedOnSinceAuthenticating;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index c487901..87f5ca7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -158,7 +158,7 @@
}
@Override
- public void onWallpaperColorsChanged(WallpaperColors colors, int which) {
+ public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
index 8bc6563..004a604 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NearestTouchFrame.java
@@ -15,7 +15,9 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Rect;
+import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
@@ -42,8 +44,13 @@
private View mTouchingChild;
public NearestTouchFrame(Context context, AttributeSet attrs) {
+ this(context, attrs, context.getResources().getConfiguration());
+ }
+
+ @VisibleForTesting
+ NearestTouchFrame(Context context, AttributeSet attrs, Configuration c) {
super(context, attrs);
- mIsActive = context.getResources().getConfiguration().smallestScreenWidthDp < 600;
+ mIsActive = c.smallestScreenWidthDp < 600;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ab021b4..5d8d01c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -142,6 +142,7 @@
import android.widget.TextView;
import android.widget.Toast;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.MetricsLogger;
@@ -749,7 +750,7 @@
private SysuiColorExtractor mColorExtractor;
private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
- private WakefulnessLifecycle mWakefulnessLifecycle;
+ @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
final int N = array.size();
@@ -3787,6 +3788,15 @@
private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
boolean afterKeyguardGone) {
+ if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
+ && mUnlockMethodCache.canSkipBouncer()
+ && !mLeaveOpenOnKeyguardHide
+ && isPulsing()) {
+ // Reuse the fingerprint wake-and-unlock transition if we dismiss keyguard from a pulse.
+ // TODO: Factor this transition out of FingerprintUnlockController.
+ mFingerprintUnlockController.startWakeAndUnlock(
+ FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
+ }
if (mStatusBarKeyguardViewManager.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
afterKeyguardGone);
@@ -4223,7 +4233,10 @@
public void showKeyguard() {
mKeyguardRequested = true;
+ mLeaveOpenOnKeyguardHide = false;
+ mPendingRemoteInputView = null;
updateIsKeyguard();
+ mAssistManager.onLockscreenShown();
}
public boolean hideKeyguard() {
@@ -4272,14 +4285,11 @@
}
updateKeyguardState(false /* goingToFullShade */, false /* fromShadeLocked */);
updatePanelExpansionForKeyguard();
- mLeaveOpenOnKeyguardHide = false;
if (mDraggedDownRow != null) {
mDraggedDownRow.setUserLocked(false);
mDraggedDownRow.notifyHeightChanged(false /* needsAnimation */);
mDraggedDownRow = null;
}
- mPendingRemoteInputView = null;
- mAssistManager.onLockscreenShown();
}
private void updatePanelExpansionForKeyguard() {
@@ -4419,15 +4429,19 @@
setBarState(StatusBarState.SHADE);
View viewToClick = null;
if (mLeaveOpenOnKeyguardHide) {
- mLeaveOpenOnKeyguardHide = false;
+ if (!mKeyguardRequested) {
+ mLeaveOpenOnKeyguardHide = false;
+ }
long delay = calculateGoingToFullShadeDelay();
mNotificationPanel.animateToFullShade(delay);
if (mDraggedDownRow != null) {
mDraggedDownRow.setUserLocked(false);
mDraggedDownRow = null;
}
- viewToClick = mPendingRemoteInputView;
- mPendingRemoteInputView = null;
+ if (!mKeyguardRequested) {
+ viewToClick = mPendingRemoteInputView;
+ mPendingRemoteInputView = null;
+ }
// Disable layout transitions in navbar for this transition because the load is just
// too heavy for the CPU and GPU on any device.
@@ -5566,6 +5580,11 @@
mStatusBarWindowManager.setDozeScreenBrightness(value);
}
+ @Override
+ public void setAodDimmingScrim(float scrimOpacity) {
+ mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ }
+
public void dispatchDoubleTap(float viewX, float viewY) {
dispatchTap(mAmbientIndicationContainer, viewX, viewY);
dispatchTap(mAmbientIndicationContainer, viewX, viewY);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e142d62..3147009 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -71,6 +71,7 @@
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
+ private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
@@ -121,6 +122,8 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
+ mDisplayBlanksAfterDoze = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
@@ -153,7 +156,6 @@
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (mBouncer.needsFullscreenBouncer() && !mDozing) {
-
// The keyguard might be showing (already). So we need to hide it.
mStatusBar.hideKeyguard();
mBouncer.show(true /* resetSecuritySelection */);
@@ -164,6 +166,7 @@
mBouncer.prepare();
}
}
+ updateStates();
}
private void showBouncer() {
@@ -247,7 +250,9 @@
public void setDozing(boolean dozing) {
if (mDozing != dozing) {
mDozing = dozing;
- reset(dozing /* hideBouncerWhenShowing */);
+ if (dozing || mBouncer.needsFullscreenBouncer() || mOccluded) {
+ reset(dozing /* hideBouncerWhenShowing */);
+ }
updateStates();
}
}
@@ -373,7 +378,11 @@
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- if (!mScreenTurnedOn) {
+ boolean turnedOnSinceAuth =
+ mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
+ if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
+ // Not ready to animate yet; either because the screen is not on yet,
+ // or it is on but will turn off before waking out of doze.
mDeferScrimFadeOut = true;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index f4197a3..c060b08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -249,14 +249,28 @@
state.paddingMap.clear();
int notGoneIndex = 0;
ExpandableView lastView = null;
+ int firstHiddenIndex = ambientState.isDark()
+ ? (ambientState.hasPulsingNotifications() ? 1 : 0)
+ : childCount;
+
+ // The goal here is to fill the padding map, by iterating over how much padding each child
+ // needs. The map is thereby reused, by first filling it with the padding amount and when
+ // iterating over it again, it's filled with the actual resolved value.
+
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
if (v == ambientState.getShelf()) {
continue;
}
+ if (i >= firstHiddenIndex) {
+ // we need normal padding now, to be in sync with what the stack calculates
+ lastView = null;
+ ExpandableViewState viewState = resultState.getViewStateForView(v);
+ viewState.hidden = true;
+ }
notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
- float increasedPadding = v.getIncreasedPaddingAmount();;
+ float increasedPadding = v.getIncreasedPaddingAmount();
if (increasedPadding != 0.0f) {
state.paddingMap.put(v, increasedPadding);
if (lastView != null) {
@@ -279,6 +293,8 @@
state.paddingMap.put(lastView, newValue);
}
} else if (lastView != null) {
+
+ // Let's now resolve the value to an actual padding
float newValue = getPaddingForValue(state.paddingMap.get(lastView));
state.paddingMap.put(lastView, newValue);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
new file mode 100644
index 0000000..5790ba3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncSensorManager.java
@@ -0,0 +1,162 @@
+/*
+ * 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.util;
+
+import android.hardware.HardwareBuffer;
+import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
+import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEventListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.MemoryFile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Wrapper around sensor manager that hides potential sources of latency.
+ *
+ * Offloads fetching (non-dynamic) sensors and (un)registering listeners onto a background thread
+ * without blocking. Note that this means registering listeners now always appears successful even
+ * if it is not.
+ */
+public class AsyncSensorManager extends SensorManager {
+
+ private static final String TAG = "AsyncSensorManager";
+
+ private final SensorManager mInner;
+ private final List<Sensor> mSensorCache;
+ private final HandlerThread mHandlerThread = new HandlerThread("async_sensor");
+ @VisibleForTesting final Handler mHandler;
+
+ public AsyncSensorManager(SensorManager inner) {
+ mInner = inner;
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mSensorCache = mInner.getSensorList(Sensor.TYPE_ALL);
+ }
+
+ @Override
+ protected List<Sensor> getFullSensorList() {
+ return mSensorCache;
+ }
+
+ @Override
+ protected List<Sensor> getFullDynamicSensorList() {
+ return mInner.getDynamicSensorList(Sensor.TYPE_ALL);
+ }
+
+ @Override
+ protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, int delayUs,
+ Handler handler, int maxReportLatencyUs, int reservedFlags) {
+ mHandler.post(() -> {
+ if (!mInner.registerListener(listener, sensor, delayUs, maxReportLatencyUs, handler)) {
+ Log.e(TAG, "Registering " + listener + " for " + sensor + " failed.");
+ }
+ });
+ return true;
+ }
+
+ @Override
+ protected boolean flushImpl(SensorEventListener listener) {
+ return mInner.flush(listener);
+ }
+
+ @Override
+ protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile,
+ HardwareBuffer hardwareBuffer) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ protected void destroyDirectChannelImpl(SensorDirectChannel channel) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback,
+ Handler handler) {
+ mHandler.post(() -> mInner.registerDynamicSensorCallback(callback, handler));
+ }
+
+ @Override
+ protected void unregisterDynamicSensorCallbackImpl(DynamicSensorCallback callback) {
+ mHandler.post(() -> mInner.unregisterDynamicSensorCallback(callback));
+ }
+
+ @Override
+ protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ mHandler.post(() -> {
+ if (!mInner.requestTriggerSensor(listener, sensor)) {
+ Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
+ }
+ });
+ return true;
+ }
+
+ @Override
+ protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+ boolean disable) {
+ Preconditions.checkArgument(disable);
+
+ mHandler.post(() -> {
+ if (!mInner.cancelTriggerSensor(listener, sensor)) {
+ Log.e(TAG, "Canceling " + listener + " for " + sensor + " failed.");
+ }
+ });
+ return true;
+ }
+
+ @Override
+ protected boolean initDataInjectionImpl(boolean enable) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy,
+ long timestamp) {
+ throw new UnsupportedOperationException("not implemented");
+ }
+
+ @Override
+ protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
+ mHandler.post(() -> mInner.setOperationParameter(parameter));
+ return true;
+ }
+
+ @Override
+ protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+ mHandler.post(() -> {
+ if (sensor == null) {
+ mInner.unregisterListener(listener);
+ } else {
+ mInner.unregisterListener(listener, sensor);
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index ad47411..b08b26d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -100,7 +100,7 @@
private final MediaSessions mMediaSessions;
protected C mCallbacks = new C();
private final State mState = new State();
- private final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
+ protected final MediaSessionsCallbacks mMediaSessionsCallbacksW = new MediaSessionsCallbacks();
private final Vibrator mVibrator;
private final boolean mHasVibrator;
private boolean mShowA11yStream;
@@ -906,7 +906,7 @@
}
}
- private final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
+ protected final class MediaSessionsCallbacks implements MediaSessions.Callbacks {
private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>();
private int mNextStream = DYNAMIC_STREAM_START_INDEX;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index fe3221a..c275806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -51,14 +51,16 @@
DozeScreenBrightness mScreen;
FakeSensorManager.FakeGenericSensor mSensor;
FakeSensorManager mSensorManager;
+ DozeHostFake mHostFake;
@Before
public void setUp() throws Exception {
mServiceFake = new DozeServiceFake();
+ mHostFake = new DozeHostFake();
mSensorManager = new FakeSensorManager(mContext);
mSensor = mSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- mSensor.getSensor(), null /* handler */);
+ mSensor.getSensor(), mHostFake, null /* handler */);
}
@Test
@@ -133,7 +135,7 @@
@Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- null /* sensor */, null /* handler */);
+ null /* sensor */, mHostFake, null /* handler */);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
index 577dc52..ed1491d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NearestTouchFrameTest.java
@@ -18,10 +18,12 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.support.test.filters.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -43,7 +45,29 @@
@Before
public void setup() {
- mNearestTouchFrame = new NearestTouchFrame(mContext, null);
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.smallestScreenWidthDp = 500;
+ mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);
+ }
+
+ @Test
+ public void testNoActionOnLargeDevices() {
+ Configuration c = new Configuration(mContext.getResources().getConfiguration());
+ c.smallestScreenWidthDp = 700;
+ mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);
+
+ View left = mockViewAt(0, 0, 10, 10);
+ View right = mockViewAt(20, 0, 10, 10);
+
+ mNearestTouchFrame.addView(left);
+ mNearestTouchFrame.addView(right);
+ mNearestTouchFrame.onMeasure(0, 0);
+
+ MotionEvent ev = MotionEvent.obtain(0, 0, 0,
+ 12 /* x */, 5 /* y */, 0);
+ mNearestTouchFrame.onTouchEvent(ev);
+ verify(left, never()).onTouchEvent(eq(ev));
+ ev.recycle();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index c33897e..a706368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -65,6 +65,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.CommandQueue;
@@ -500,6 +501,14 @@
mSystemServicesProxy = ssp;
mNotificationPanel = panelView;
mBarService = barService;
+ mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
+ }
+
+ private WakefulnessLifecycle createAwakeWakefulnessLifecycle() {
+ WakefulnessLifecycle wakefulnessLifecycle = new WakefulnessLifecycle();
+ wakefulnessLifecycle.dispatchStartedWakingUp();
+ wakefulnessLifecycle.dispatchFinishedWakingUp();
+ return wakefulnessLifecycle;
}
public void setBarStateForTest(int state) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
new file mode 100644
index 0000000..469bdc0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/AsyncSensorManagerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.hardware.FakeSensorManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AsyncSensorManagerTest extends SysuiTestCase {
+
+ private TestableAsyncSensorManager mAsyncSensorManager;
+ private FakeSensorManager mFakeSensorManager;
+ private SensorEventListener mListener;
+ private FakeSensorManager.MockProximitySensor mSensor;
+
+ @Before
+ public void setUp() throws Exception {
+ mFakeSensorManager = new FakeSensorManager(mContext);
+ mAsyncSensorManager = new TestableAsyncSensorManager(mFakeSensorManager);
+ mSensor = mFakeSensorManager.getMockProximitySensor();
+ mListener = mock(SensorEventListener.class);
+ }
+
+ @Test
+ public void registerListenerImpl() throws Exception {
+ mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+
+ mAsyncSensorManager.waitUntilRequestsCompleted();
+
+ // Verify listener was registered.
+ mSensor.sendProximityResult(true);
+ verify(mListener).onSensorChanged(any());
+ }
+
+ @Test
+ public void unregisterListenerImpl_withNullSensor() throws Exception {
+ mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+ mAsyncSensorManager.unregisterListener(mListener);
+
+ mAsyncSensorManager.waitUntilRequestsCompleted();
+
+ // Verify listener was unregistered.
+ mSensor.sendProximityResult(true);
+ verifyNoMoreInteractions(mListener);
+ }
+
+ @Test
+ public void unregisterListenerImpl_withSensor() throws Exception {
+ mAsyncSensorManager.registerListener(mListener, mSensor.getSensor(), 100);
+ mAsyncSensorManager.unregisterListener(mListener, mSensor.getSensor());
+
+ mAsyncSensorManager.waitUntilRequestsCompleted();
+
+ // Verify listener was unregistered.
+ mSensor.sendProximityResult(true);
+ verifyNoMoreInteractions(mListener);
+ }
+
+ private class TestableAsyncSensorManager extends AsyncSensorManager {
+ public TestableAsyncSensorManager(SensorManager sensorManager) {
+ super(sensorManager);
+ }
+
+ public void waitUntilRequestsCompleted() {
+ assertTrue(mHandler.runWithScissors(() -> {}, 0));
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 12e9f7c..06568f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.media.AudioManager;
+import android.media.session.MediaSession;
import android.support.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -73,6 +74,18 @@
verify(mCallback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
}
+ @Test
+ public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
+ MediaSession.Token token = new MediaSession.Token(null);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
+ }
+
+ @Test
+ public void testOnRemoteRemove_newStream_noNullPointer() {
+ MediaSession.Token token = new MediaSession.Token(null);
+ mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
+ }
+
static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
public TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s) {
super(context);
diff --git a/services/Android.mk b/services/Android.mk
index 0986e0a..ed2ba1f8 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -40,8 +40,8 @@
# The convention is to name each service module 'services.$(module_name)'
LOCAL_STATIC_JAVA_LIBRARIES := $(addprefix services.,$(services)) \
- android.hidl.base-V1.0-java-static \
- android.hardware.biometrics.fingerprint-V2.1-java-static
+ android.hidl.base-V1.0-java \
+ android.hardware.biometrics.fingerprint-V2.1-java
LOCAL_JAVA_LIBRARIES := \
android.hidl.manager-V1.0-java
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 94004ce..5e188e0 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -27,6 +27,7 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
time_zone_distro \
time_zone_distro_installer \
+ android.hidl.base-V1.0-java \
android.hardware.weaver-V1.0-java \
android.hardware.biometrics.fingerprint-V2.1-java \
android.hardware.oemlock-V1.0-java \
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index fdc0bba..d60df83 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -27,6 +27,7 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
+import android.location.LocationManager;
import android.net.INetworkRecommendationProvider;
import android.net.INetworkScoreCache;
import android.net.INetworkScoreService;
@@ -113,6 +114,16 @@
}
};
+ private BroadcastReceiver mLocationModeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
+ refreshBinding();
+ }
+ }
+ };
+
/**
* Clears scores when the active scorer package is no longer valid and
* manages the service connection.
@@ -241,6 +252,10 @@
mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
null /* scheduler */);
mHandler = new ServiceHandler(looper);
+ IntentFilter locationModeFilter = new IntentFilter(LocationManager.MODE_CHANGED_ACTION);
+ mContext.registerReceiverAsUser(
+ mLocationModeReceiver, UserHandle.SYSTEM, locationModeFilter,
+ null /* broadcastPermission*/, mHandler);
mContentObserver = new DispatchingContentObserver(context, mHandler);
mServiceConnProducer = serviceConnProducer;
}
diff --git a/services/core/java/com/android/server/NetworkScorerAppManager.java b/services/core/java/com/android/server/NetworkScorerAppManager.java
index 42777bf..bfd4247 100644
--- a/services/core/java/com/android/server/NetworkScorerAppManager.java
+++ b/services/core/java/com/android/server/NetworkScorerAppManager.java
@@ -18,6 +18,7 @@
import android.Manifest.permission;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -79,7 +80,7 @@
List<NetworkScorerAppData> appDataList = new ArrayList<>();
for (int i = 0; i < resolveInfos.size(); i++) {
final ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo;
- if (hasPermissions(serviceInfo.packageName)) {
+ if (hasPermissions(serviceInfo.applicationInfo.uid, serviceInfo.packageName)) {
if (VERBOSE) {
Log.v(TAG, serviceInfo.packageName + " is a valid scorer/recommender.");
}
@@ -197,12 +198,33 @@
return null;
}
- private boolean hasPermissions(String packageName) {
+ private boolean hasPermissions(final int uid, final String packageName) {
+ return hasScoreNetworksPermission(packageName)
+ && canAccessLocation(uid, packageName);
+ }
+
+ private boolean hasScoreNetworksPermission(String packageName) {
final PackageManager pm = mContext.getPackageManager();
return pm.checkPermission(permission.SCORE_NETWORKS, packageName)
== PackageManager.PERMISSION_GRANTED;
}
+ private boolean canAccessLocation(int uid, String packageName) {
+ final PackageManager pm = mContext.getPackageManager();
+ final AppOpsManager appOpsManager =
+ (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ return isLocationModeEnabled()
+ && pm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName)
+ == PackageManager.PERMISSION_GRANTED
+ && appOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName)
+ == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private boolean isLocationModeEnabled() {
+ return mSettingsFacade.getSecureInt(mContext, Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF) != Settings.Secure.LOCATION_MODE_OFF;
+ }
+
/**
* Set the specified package as the default scorer application.
*
@@ -270,23 +292,20 @@
return;
}
- // the active scorer isn't valid, revert to the default if it's different
+ int newEnabledSetting = NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF;
+ // the active scorer isn't valid, revert to the default if it's different and valid
final String defaultPackageName = getDefaultPackageSetting();
- if (!TextUtils.equals(currentPackageName, defaultPackageName)) {
- setNetworkRecommendationsPackage(defaultPackageName);
+ if (!TextUtils.equals(currentPackageName, defaultPackageName)
+ && getScorer(defaultPackageName) != null) {
if (DEBUG) {
- Log.d(TAG, "Defaulted the network recommendations app to: " + defaultPackageName);
+ Log.d(TAG, "Defaulting the network recommendations app to: "
+ + defaultPackageName);
}
- if (getScorer(defaultPackageName) != null) { // the default is valid
- if (DEBUG) Log.d(TAG, defaultPackageName + " is now the active scorer.");
- setNetworkRecommendationsEnabledSetting(
- NetworkScoreManager.RECOMMENDATIONS_ENABLED_ON);
- } else { // the default isn't valid either, we're disabled at this point
- if (DEBUG) Log.d(TAG, defaultPackageName + " is not an active scorer.");
- setNetworkRecommendationsEnabledSetting(
- NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
- }
+ setNetworkRecommendationsPackage(defaultPackageName);
+ newEnabledSetting = NetworkScoreManager.RECOMMENDATIONS_ENABLED_ON;
}
+
+ setNetworkRecommendationsEnabledSetting(newEnabledSetting);
}
/**
@@ -352,6 +371,9 @@
private void setNetworkRecommendationsPackage(String packageName) {
mSettingsFacade.putString(mContext,
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, packageName);
+ if (VERBOSE) {
+ Log.d(TAG, Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE + " set to " + packageName);
+ }
}
private int getNetworkRecommendationsEnabledSetting() {
@@ -361,6 +383,9 @@
private void setNetworkRecommendationsEnabledSetting(int value) {
mSettingsFacade.putInt(mContext,
Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, value);
+ if (VERBOSE) {
+ Log.d(TAG, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED + " set to " + value);
+ }
}
/**
@@ -382,5 +407,9 @@
public int getInt(Context context, String name, int defaultValue) {
return Settings.Global.getInt(context.getContentResolver(), name, defaultValue);
}
+
+ public int getSecureInt(Context context, String name, int defaultValue) {
+ return Settings.Secure.getInt(context.getContentResolver(), name, defaultValue);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ddc524f..54ea2c7f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -714,7 +714,9 @@
public boolean canShowErrorDialogs() {
return mShowDialogs && !mSleeping && !mShuttingDown
- && !mKeyguardController.isKeyguardShowing();
+ && !mKeyguardController.isKeyguardShowing()
+ && !(UserManager.isDeviceInDemoMode(mContext)
+ && mUserController.getCurrentUser().isDemo());
}
private static ThreadPriorityBooster sThreadPriorityBooster = new ThreadPriorityBooster(
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 2aa5350..7a19cc3 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -2161,7 +2161,7 @@
if (mStartingWindowState == STARTING_WINDOW_SHOWN && behindFullscreenActivity) {
if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
mStartingWindowState = STARTING_WINDOW_REMOVED;
- mWindowContainerController.removeHiddenStartingWindow();
+ mWindowContainerController.removeStartingWindow();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9925ba0..79448ca 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -650,6 +650,13 @@
return topRunningActivityLocked(false /* focusableOnly */);
}
+ void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
+ outActivities.clear();
+ for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+ mTaskHistory.get(taskNdx).getAllRunningVisibleActivitiesLocked(outActivities);
+ }
+ }
+
private ActivityRecord topRunningActivityLocked(boolean focusableOnly) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5f42cdb..e8bc68f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -441,6 +441,8 @@
final ActivityMetricsLogger mActivityMetricsLogger;
+ private final ArrayList<ActivityRecord> mTmpActivityList = new ArrayList<>();
+
@Override
protected int getChildCount() {
return mActivityDisplays.size();
@@ -954,17 +956,21 @@
if (!isFocusedStack(stack)) {
continue;
}
- ActivityRecord hr = stack.topRunningActivityLocked();
- if (hr != null) {
- if (hr.app == null && app.uid == hr.info.applicationInfo.uid
- && processName.equals(hr.processName)) {
+ stack.getAllRunningVisibleActivitiesLocked(mTmpActivityList);
+ final ActivityRecord top = stack.topRunningActivityLocked();
+ final int size = mTmpActivityList.size();
+ for (int i = 0; i < size; i++) {
+ final ActivityRecord activity = mTmpActivityList.get(i);
+ if (activity.app == null && app.uid == activity.info.applicationInfo.uid
+ && processName.equals(activity.processName)) {
try {
- if (realStartActivityLocked(hr, app, true, true)) {
+ if (realStartActivityLocked(activity, app,
+ top == activity /* andResume */, true /* checkConfig */)) {
didSomething = true;
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting activity "
- + hr.intent.getComponent().flattenToShortString(), e);
+ + top.intent.getComponent().flattenToShortString(), e);
throw e;
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5753fbc..be0a1d9 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1159,6 +1159,17 @@
return null;
}
+ void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
+ if (mStack != null) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = mActivities.get(activityNdx);
+ if (!r.finishing && r.okToShowLocked() && r.visible) {
+ outActivities.add(r);
+ }
+ }
+ }
+ }
+
ActivityRecord topRunningActivityWithStartingWindowLocked() {
if (mStack != null) {
for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 89deb49..95d7e9f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -669,12 +669,6 @@
}
if (stopped) {
- // Evict the user's credential encryption key
- try {
- getStorageManager().lockUserKey(userId);
- } catch (RemoteException re) {
- throw re.rethrowAsRuntimeException();
- }
mInjector.systemServiceManagerCleanupUser(userId);
synchronized (mLock) {
mInjector.getActivityStackSupervisor().removeUserLocked(userId);
@@ -683,6 +677,12 @@
if (getUserInfo(userId).isEphemeral()) {
mInjector.getUserManager().removeUser(userId);
}
+ // Evict the user's credential encryption key.
+ try {
+ getStorageManager().lockUserKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ }
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/TunerCallback.java
index 25f3775..331b657 100644
--- a/services/core/java/com/android/server/broadcastradio/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/TunerCallback.java
@@ -86,8 +86,8 @@
}
@Override
- public void onProgramInfoChanged() {
- dispatch(() -> mClientCallback.onProgramInfoChanged());
+ public void onCurrentProgramInfoChanged() {
+ dispatch(() -> mClientCallback.onCurrentProgramInfoChanged());
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 207bdba..f7efff0f 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -186,7 +186,12 @@
private static String hintsToString(int hints) {
switch (hints) {
case 0 : return "none";
- case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS : return "disable_effects";
+ case NotificationListenerService.HINT_HOST_DISABLE_EFFECTS:
+ return "disable_effects";
+ case NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS:
+ return "disable_call_effects";
+ case NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS:
+ return "disable_notification_effects";
default: return Integer.toString(hints);
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9d6748c..33615ba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -10004,6 +10004,7 @@
public void shutdown() {
mPackageUsage.writeNow(mPackages);
mCompilerStats.writeNow();
+ mDexManager.savePackageDexUsageNow();
}
@Override
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 79e02b5..947e01c4 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -592,6 +592,13 @@
return existingValue == null ? newValue : existingValue;
}
+ /**
+ * Saves the in-memory package dex usage to disk right away.
+ */
+ public void savePackageDexUsageNow() {
+ mPackageDexUsage.writeNow();
+ }
+
public static class RegisterDexModuleResult {
public RegisterDexModuleResult() {
this(false, null);
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 8819aa6..6ee26d3 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -198,8 +198,12 @@
* Convenience method for async writes which does not force the user to pass a useless
* (Void) null.
*/
- public void maybeWriteAsync() {
- maybeWriteAsync((Void) null);
+ /*package*/ void maybeWriteAsync() {
+ maybeWriteAsync(null);
+ }
+
+ /*package*/ void writeNow() {
+ writeInternal(null);
}
@Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e3cf459..6afd69d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4184,7 +4184,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
boolean handled = false;
try {
if (event instanceof MotionEvent
@@ -7049,6 +7049,12 @@
// Ignore sensor when demo rotation lock is enabled.
// Note that the dock orientation and HDMI rotation lock override this.
preferredRotation = mDemoRotation;
+ } else if (mPersistentVrModeEnabled) {
+ // While in VR, apps always prefer a portrait rotation. This does not change
+ // any apps that explicitly set landscape, but does cause sensors be ignored,
+ // and ignored any orientation lock that the user has set (this conditional
+ // should remain above the ORIENTATION_LOCKED conditional below).
+ preferredRotation = mPortraitRotation;
} else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {
// Application just wants to remain locked in the last rotation.
preferredRotation = lastRotation;
@@ -7079,13 +7085,7 @@
|| mAllowAllRotations == 1
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|| orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
- // In VrMode, we report the sensor as always being in default orientation so:
- // 1) The orientation doesn't change as the user moves their head.
- // 2) 2D apps within VR show in the device's default orientation.
- // This only overwrites the sensor-provided orientation and does not affect any
- // explicit orientation preferences specified by any activities.
- preferredRotation =
- mPersistentVrModeEnabled ? Surface.ROTATION_0 : sensorRotation;
+ preferredRotation = sensorRotation;
} else {
preferredRotation = lastRotation;
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index e894275..92f1c83 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -57,6 +57,7 @@
import android.widget.TextView;
import com.android.internal.telephony.ITelephony;
+import com.android.server.RescueParty;
import com.android.server.pm.PackageManagerService;
import java.io.File;
@@ -293,11 +294,20 @@
com.android.internal.R.string.reboot_to_update_reboot));
}
} else if (mReason != null && mReason.equals(PowerManager.REBOOT_RECOVERY)) {
- // Factory reset path. Set the dialog message accordingly.
- pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
- pd.setMessage(context.getText(
- com.android.internal.R.string.reboot_to_reset_message));
- pd.setIndeterminate(true);
+ if (RescueParty.isAttemptingFactoryReset()) {
+ // We're not actually doing a factory reset yet; we're rebooting
+ // to ask the user if they'd like to reset, so give them a less
+ // scary dialog message.
+ pd.setTitle(context.getText(com.android.internal.R.string.power_off));
+ pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
+ pd.setIndeterminate(true);
+ } else {
+ // Factory reset path. Set the dialog message accordingly.
+ pd.setTitle(context.getText(com.android.internal.R.string.reboot_to_reset_title));
+ pd.setMessage(context.getText(
+ com.android.internal.R.string.reboot_to_reset_message));
+ pd.setIndeterminate(true);
+ }
} else if (mReason != null && mReason.equals(PowerManager.SHUTDOWN_USER_REQUESTED)) {
Dialog d = new Dialog(context);
d.setContentView(com.android.internal.R.layout.shutdown_dialog);
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
index 88b6d87..a35383f 100644
--- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -29,6 +29,7 @@
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Message;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -154,20 +155,8 @@
private static final String TV_NOTIFICATION_CHANNEL_ID = "devicestoragemonitor.tv";
- /**
- * Handler that checks the amount of disk space on the device and sends a
- * notification if the device runs low on disk space
- */
- private final Handler mHandler = new Handler(IoThread.get().getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CHECK:
- check();
- return;
- }
- }
- };
+ private final HandlerThread mHandlerThread;
+ private final Handler mHandler;
private State findOrCreateState(UUID uuid) {
State state = mStates.get(uuid);
@@ -256,6 +245,20 @@
public DeviceStorageMonitorService(Context context) {
super(context);
+
+ mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+
+ mHandler = new Handler(mHandlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CHECK:
+ check();
+ return;
+ }
+ }
+ };
}
private static boolean isBootImageOnDisk() {
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 69d8ca6..8f50a39 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -24,6 +24,7 @@
import android.service.vr.IVrManager;
import android.util.Log;
import android.view.Surface;
+import android.view.WindowManagerInternal;
import com.android.server.vr.VrManagerService;
@@ -74,6 +75,7 @@
public static final int MIN_VR_DISPLAY_DPI = 1;
private final ActivityManagerInternal mActivityManagerInternal;
+ private final WindowManagerInternal mWindowManagerInternal;
private final DisplayManager mDisplayManager;
private final IVrManager mVrManager;
private final Object mVdLock = new Object();
@@ -103,9 +105,11 @@
private boolean mBootsToVr = false; // The device boots into VR (standalone VR device)
public Vr2dDisplay(DisplayManager displayManager,
- ActivityManagerInternal activityManagerInternal, IVrManager vrManager) {
+ ActivityManagerInternal activityManagerInternal,
+ WindowManagerInternal windowManagerInternal, IVrManager vrManager) {
mDisplayManager = displayManager;
mActivityManagerInternal = activityManagerInternal;
+ mWindowManagerInternal = windowManagerInternal;
mVrManager = vrManager;
mVirtualDisplayWidth = DEFAULT_VIRTUAL_DISPLAY_WIDTH;
mVirtualDisplayHeight = DEFAULT_VIRTUAL_DISPLAY_HEIGHT;
@@ -296,13 +300,12 @@
UNIQUE_DISPLAY_ID);
if (mVirtualDisplay != null) {
- mActivityManagerInternal.setVr2dDisplayId(
- mVirtualDisplay.getDisplay().getDisplayId());
+ updateDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
// Now create the ImageReader to supply a Surface to the new virtual display.
startImageReader();
} else {
Log.w(TAG, "Virtual display id is null after createVirtualDisplay");
- mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY);
+ updateDisplayId(INVALID_DISPLAY);
return;
}
}
@@ -310,6 +313,11 @@
Log.i(TAG, "VD created: " + mVirtualDisplay);
}
+ private void updateDisplayId(int displayId) {
+ mActivityManagerInternal.setVr2dDisplayId(displayId);
+ mWindowManagerInternal.setVr2dDisplayId(displayId);
+ }
+
/**
* Stops the virtual display with a {@link #STOP_VIRTUAL_DISPLAY_DELAY_MILLIS} timeout.
* The timeout prevents the virtual display from bouncing in cases where VrMode goes in and out
@@ -325,7 +333,7 @@
} else {
Log.i(TAG, "Stopping Virtual Display");
synchronized (mVdLock) {
- mActivityManagerInternal.setVr2dDisplayId(INVALID_DISPLAY);
+ updateDisplayId(INVALID_DISPLAY);
setSurfaceLocked(null); // clean up and release the surface first.
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 425b23f..b6b964b 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -58,6 +58,7 @@
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.WindowManagerInternal;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
@@ -633,8 +634,11 @@
DisplayManager dm =
(DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
- ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- mVr2dDisplay = new Vr2dDisplay(dm, ami, mVrManager);
+ mVr2dDisplay = new Vr2dDisplay(
+ dm,
+ LocalServices.getService(ActivityManagerInternal.class),
+ LocalServices.getService(WindowManagerInternal.class),
+ mVrManager);
mVr2dDisplay.init(getContext(), mBootsToVr);
IntentFilter intentFilter = new IntentFilter();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 802054e..46097ed 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -25,6 +25,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -63,6 +64,7 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IInterface;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.Process;
@@ -331,11 +333,18 @@
}
}
- private void notifyWallpaperColorsChanged(WallpaperData wallpaper, int which) {
+ private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
boolean needsExtraction;
synchronized (mLock) {
- if (mColorsChangedListeners.getRegisteredCallbackCount() == 0)
+ final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
+ mColorsChangedListeners.get(wallpaper.userId);
+ final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
+ mColorsChangedListeners.get(UserHandle.USER_ALL);
+ // No-op until someone is listening to it.
+ if (emptyCallbackList(currentUserColorListeners) &&
+ emptyCallbackList(userAllColorListeners)) {
return;
+ }
if (DEBUG) {
Slog.v(TAG, "notifyWallpaperColorsChanged " + which);
@@ -346,40 +355,66 @@
// Let's notify the current values, it's fine if it's null, it just means
// that we don't know yet.
- notifyColorListeners(wallpaper.primaryColors, which);
+ notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
if (needsExtraction) {
extractColors(wallpaper);
- notifyColorListeners(wallpaper.primaryColors, which);
+ synchronized (mLock) {
+ // Don't need to notify if nothing changed.
+ if (wallpaper.primaryColors == null) {
+ return;
+ }
+ }
+ notifyColorListeners(wallpaper.primaryColors, which, wallpaper.userId);
}
}
- private void notifyColorListeners(WallpaperColors wallpaperColors, int which) {
- final IWallpaperManagerCallback[] listeners;
+ private static <T extends IInterface> boolean emptyCallbackList(RemoteCallbackList<T> list) {
+ return (list == null || list.getRegisteredCallbackCount() == 0);
+ }
+
+ private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
+ int userId) {
final IWallpaperManagerCallback keyguardListener;
+ final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners;
+ final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners;
synchronized (mLock) {
- // Make a synchronized copy of the listeners to avoid concurrent list modification.
- int callbackCount = mColorsChangedListeners.beginBroadcast();
- listeners = new IWallpaperManagerCallback[callbackCount];
- for (int i = 0; i < callbackCount; i++) {
- listeners[i] = mColorsChangedListeners.getBroadcastItem(i);
- }
- mColorsChangedListeners.finishBroadcast();
+ currentUserColorListeners = mColorsChangedListeners.get(userId);
+ userAllColorListeners = mColorsChangedListeners.get(UserHandle.USER_ALL);
keyguardListener = mKeyguardListener;
}
- for (int i = 0; i < listeners.length; i++) {
- try {
- listeners[i].onWallpaperColorsChanged(wallpaperColors, which);
- } catch (RemoteException e) {
- // Callback is gone, it's not necessary to unregister it since
- // RemoteCallbackList#getBroadcastItem will take care of it.
+ if (currentUserColorListeners != null) {
+ int count = currentUserColorListeners.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ currentUserColorListeners.getBroadcastItem(i)
+ .onWallpaperColorsChanged(wallpaperColors, which, userId);
+ } catch (RemoteException e) {
+ // Callback is gone, it's not necessary to unregister it since
+ // RemoteCallbackList#getBroadcastItem will take care of it.
+ }
}
+ currentUserColorListeners.finishBroadcast();
+ }
+
+ if (userAllColorListeners != null) {
+ int count = userAllColorListeners.beginBroadcast();
+ for (int i = 0; i < count; i++) {
+ try {
+ userAllColorListeners.getBroadcastItem(i)
+ .onWallpaperColorsChanged(wallpaperColors, which, userId);
+ } catch (RemoteException e) {
+ // Callback is gone, it's not necessary to unregister it since
+ // RemoteCallbackList#getBroadcastItem will take care of it.
+ }
+ }
+ userAllColorListeners.finishBroadcast();
}
if (keyguardListener != null) {
try {
- keyguardListener.onWallpaperColorsChanged(wallpaperColors, which);
+ keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
} catch (RemoteException e) {
// Oh well it went away; no big deal
}
@@ -595,7 +630,11 @@
final IPackageManager mIPackageManager;
final MyPackageMonitor mMonitor;
final AppOpsManager mAppOpsManager;
- final RemoteCallbackList<IWallpaperManagerCallback> mColorsChangedListeners;
+ /**
+ * Map of color listeners per user id.
+ * The key will be the id of a user or UserHandle.USER_ALL - for wildcard listeners.
+ */
+ final SparseArray<RemoteCallbackList<IWallpaperManagerCallback>> mColorsChangedListeners;
WallpaperData mLastWallpaper;
IWallpaperManagerCallback mKeyguardListener;
boolean mWaitingForUnlock;
@@ -858,11 +897,6 @@
*/
@Override
public void onWallpaperColorsChanged(WallpaperColors primaryColors) {
- // Do not override default color extraction if API isn't implemented.
- if (primaryColors == null) {
- return;
- }
-
int which;
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
@@ -876,7 +910,7 @@
// Live wallpapers always are system wallpapers.
which = FLAG_SYSTEM;
// It's also the lock screen wallpaper when we don't have a bitmap in there
- WallpaperData lockedWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+ WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
if (lockedWallpaper == null) {
which |= FLAG_LOCK;
}
@@ -906,6 +940,13 @@
}
mPaddingChanged = false;
}
+ try {
+ // This will trigger onComputeColors in the wallpaper engine.
+ // It's fine to be locked in here since the binder is oneway.
+ mEngine.requestWallpaperColors();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to request wallpaper colors", e);
+ }
}
}
@@ -1096,7 +1137,7 @@
mMonitor.register(context, null, UserHandle.ALL, true);
getWallpaperDir(UserHandle.USER_SYSTEM).mkdirs();
loadSettingsLocked(UserHandle.USER_SYSTEM, false);
- mColorsChangedListeners = new RemoteCallbackList<>();
+ mColorsChangedListeners = new SparseArray<>();
}
private static File getWallpaperDir(int userId) {
@@ -1252,16 +1293,24 @@
}
void switchUser(int userId, IRemoteCallback reply) {
+ WallpaperData systemWallpaper;
+ WallpaperData lockWallpaper;
synchronized (mLock) {
mCurrentUserId = userId;
- WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
- // Not started watching yet, in case wallpaper data was loaded for other reasons.
- if (wallpaper.wallpaperObserver == null) {
- wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
- wallpaper.wallpaperObserver.startWatching();
+ systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+ lockWallpaper = mLockWallpaperMap.get(userId);
+ if (lockWallpaper == null) {
+ lockWallpaper = systemWallpaper;
}
- switchWallpaper(wallpaper, reply);
+ // Not started watching yet, in case wallpaper data was loaded for other reasons.
+ if (systemWallpaper.wallpaperObserver == null) {
+ systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
+ systemWallpaper.wallpaperObserver.startWatching();
+ }
+ switchWallpaper(systemWallpaper, reply);
}
+ notifyWallpaperColorsChanged(systemWallpaper, FLAG_SYSTEM);
+ notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
}
void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
@@ -1634,16 +1683,30 @@
}
@Override
- public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+ public void registerWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, true, "registerWallpaperColorsCallback", null);
synchronized (mLock) {
- mColorsChangedListeners.register(cb);
+ RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
+ mColorsChangedListeners.get(userId);
+ if (userColorsChangedListeners == null) {
+ userColorsChangedListeners = new RemoteCallbackList<>();
+ mColorsChangedListeners.put(userId, userColorsChangedListeners);
+ }
+ userColorsChangedListeners.register(cb);
}
}
@Override
- public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb) {
+ public void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, true, "unregisterWallpaperColorsCallback", null);
synchronized (mLock) {
- mColorsChangedListeners.unregister(cb);
+ final RemoteCallbackList<IWallpaperManagerCallback> userColorsChangedListeners =
+ mColorsChangedListeners.get(userId);
+ if (userColorsChangedListeners != null) {
+ userColorsChangedListeners.unregister(cb);
+ }
}
}
@@ -1657,23 +1720,25 @@
}
@Override
- public WallpaperColors getWallpaperColors(int which) throws RemoteException {
+ public WallpaperColors getWallpaperColors(int which, int userId) throws RemoteException {
if (which != FLAG_LOCK && which != FLAG_SYSTEM) {
throw new IllegalArgumentException("which should be either FLAG_LOCK or FLAG_SYSTEM");
}
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, false, true, "getWallpaperColors", null);
WallpaperData wallpaperData = null;
boolean shouldExtract;
synchronized (mLock) {
if (which == FLAG_LOCK) {
- wallpaperData = mLockWallpaperMap.get(mCurrentUserId);
+ wallpaperData = mLockWallpaperMap.get(userId);
}
// Try to get the system wallpaper anyway since it might
// also be the lock screen wallpaper
if (wallpaperData == null) {
- wallpaperData = mWallpaperMap.get(mCurrentUserId);
+ wallpaperData = mWallpaperMap.get(userId);
}
if (wallpaperData == null) {
@@ -1872,8 +1937,11 @@
try {
wallpaper.imageWallpaperPending = false;
+ boolean same = changingToSame(name, wallpaper);
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
- wallpaper.primaryColors = null;
+ if (!same) {
+ wallpaper.primaryColors = null;
+ }
wallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(wallpaper);
shouldNotifyColors = true;
@@ -1888,26 +1956,31 @@
}
}
+ private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
+ if (wallpaper.connection != null) {
+ if (wallpaper.wallpaperComponent == null) {
+ if (componentName == null) {
+ if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+ // Still using default wallpaper.
+ return true;
+ }
+ } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+ // Changing to same wallpaper.
+ if (DEBUG) Slog.v(TAG, "same wallpaper");
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
if (DEBUG_LIVE) {
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
}
// Has the component changed?
- if (!force) {
- if (wallpaper.connection != null) {
- if (wallpaper.wallpaperComponent == null) {
- if (componentName == null) {
- if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
- // Still using default wallpaper.
- return true;
- }
- } else if (wallpaper.wallpaperComponent.equals(componentName)) {
- // Changing to same wallpaper.
- if (DEBUG) Slog.v(TAG, "same wallpaper");
- return true;
- }
- }
+ if (!force && changingToSame(componentName, wallpaper)) {
+ return true;
}
try {
@@ -2234,15 +2307,35 @@
}
private void migrateFromOld() {
- File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
- File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
- if (oldWallpaper.exists()) {
- File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
- oldWallpaper.renameTo(newWallpaper);
- }
- if (oldInfo.exists()) {
- File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
- oldInfo.renameTo(newInfo);
+ // Pre-N, what existed is the one we're now using as the display crop
+ File preNWallpaper = new File(getWallpaperDir(0), WALLPAPER_CROP);
+ // In the very-long-ago, imagery lived with the settings app
+ File originalWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
+ File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
+
+ // Migrations from earlier wallpaper image storage schemas
+ if (preNWallpaper.exists()) {
+ if (!newWallpaper.exists()) {
+ // we've got the 'wallpaper' crop file but not the nominal source image,
+ // so do the simple "just take everything" straight copy of legacy data
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating wallpaper schema");
+ }
+ FileUtils.copyFile(preNWallpaper, newWallpaper);
+ } // else we're in the usual modern case: both source & crop exist
+ } else if (originalWallpaper.exists()) {
+ // VERY old schema; make sure things exist and are in the right place
+ if (DEBUG) {
+ Slog.i(TAG, "Migrating antique wallpaper schema");
+ }
+ File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
+ if (oldInfo.exists()) {
+ File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
+ oldInfo.renameTo(newInfo);
+ }
+
+ FileUtils.copyFile(originalWallpaper, preNWallpaper);
+ originalWallpaper.renameTo(newWallpaper);
}
}
@@ -2303,12 +2396,12 @@
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
- if (!file.exists()) {
- // This should only happen one time, when upgrading from a legacy system
- migrateFromOld();
- }
+
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper == null) {
+ // Do this once per boot
+ migrateFromOld();
+
wallpaper = new WallpaperData(userId, WALLPAPER, WALLPAPER_CROP);
wallpaper.allowBackup = true;
mWallpaperMap.put(userId, wallpaper);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 78f2195..805250a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -17,7 +17,7 @@
package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -539,7 +539,7 @@
WindowState windowState = visibleWindows.valueAt(i);
if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
|| ((windowState.mAttrs.privateFlags
- & PRIVATE_FLAG_NO_MAGNIFICATION_REGION_EFFECT) != 0)) {
+ & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
continue;
}
diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java
index 4a04af5..e9696d2 100644
--- a/services/core/java/com/android/server/wm/AppWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java
@@ -611,23 +611,7 @@
return mContainer.getTask().getConfiguration().orientation == snapshot.getOrientation();
}
- /**
- * Remove starting window if the app is currently hidden. It is possible the starting window is
- * part of its app exit transition animation in which case we delay hiding the app token. The
- * method allows for removal when window manager has set the app token to hidden.
- */
- public void removeHiddenStartingWindow() {
- synchronized (mWindowMap) {
- if (!mContainer.hidden) {
- if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Starting window app still visible."
- + " Ignoring remove request.");
- return;
- }
- removeStartingWindow();
- }
- }
-
- void removeStartingWindow() {
+ public void removeStartingWindow() {
synchronized (mWindowMap) {
if (mContainer.startingWindow == null) {
if (mContainer.startingData != null) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index c4ff455..be4b43d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -290,7 +290,7 @@
boolean nowGone = mReportedVisibilityResults.nowGone;
boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
- boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting;
+ boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting && !hidden;
if (!nowGone) {
// If the app is not yet gone, then it can only become visible/drawn.
if (!nowDrawn) {
@@ -448,7 +448,6 @@
mChildren.get(i).mWinAnimator.hide("immediately hidden");
}
SurfaceControl.closeTransaction();
- removeStartingWindow();
}
if (!mService.mClosingApps.contains(this) && !mService.mOpeningApps.contains(this)) {
@@ -526,12 +525,6 @@
return super.checkCompleteDeferredRemoval();
}
- private void removeStartingWindow() {
- if (startingData != null && getController() != null) {
- getController().removeStartingWindow();
- }
- }
-
void onRemovedFromDisplay() {
if (mRemovingFromDisplay) {
return;
@@ -559,7 +552,9 @@
if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG_WM, "removeAppToken: "
+ this + " delayed=" + delayed + " Callers=" + Debug.getCallers(4));
- removeStartingWindow();
+ if (startingData != null && getController() != null) {
+ getController().removeStartingWindow();
+ }
// If this window was animating, then we need to ensure that the app transition notifies
// that animations have completed in WMS.handleAnimatingStoppedAndTransitionLocked(), so
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
index 6b0e4c9..484987e 100644
--- a/services/core/java/com/android/server/wm/PointerEventDispatcher.java
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -36,11 +36,11 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
try {
if (event instanceof MotionEvent
&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- final MotionEvent motionEvent = (MotionEvent)event;
+ final MotionEvent motionEvent = (MotionEvent) event;
PointerEventListener[] listeners;
synchronized (mListeners) {
if (mListenersArray == null) {
@@ -50,7 +50,7 @@
listeners = mListenersArray;
}
for (int i = 0; i < listeners.length; ++i) {
- listeners[i].onPointerEvent(motionEvent);
+ listeners[i].onPointerEvent(motionEvent, displayId);
}
}
} finally {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 233e75b..40528d0 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -18,6 +18,7 @@
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.power.V1_0.PowerHint;
import android.os.Binder;
import android.os.Debug;
@@ -243,12 +244,24 @@
displayId, displayInfo);
mService.configureDisplayPolicyLocked(dc);
- // TODO(multi-display): Create an input channel for each display with touch capability.
- if (displayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
- dc.mTapDetector = new TaskTapPointerEventListener(
- mService, dc);
+ // Tap Listeners are supported for:
+ // 1. All physical displays (multi-display).
+ // 2. VirtualDisplays that support virtual touch input. (Only VR for now)
+ // TODO(multi-display): Support VirtualDisplays with no virtual touch input.
+ if ((display.getType() != Display.TYPE_VIRTUAL
+ || (display.getType() == Display.TYPE_VIRTUAL
+ // Only VR VirtualDisplays
+ && displayId == mService.mVr2dDisplayId))
+ && mService.canDispatchPointerEvents()) {
+ if (DEBUG_DISPLAY) {
+ Slog.d(TAG,
+ "Registering PointerEventListener for DisplayId: " + displayId);
+ }
+ dc.mTapDetector = new TaskTapPointerEventListener(mService, dc);
mService.registerPointerEventListener(dc.mTapDetector);
- mService.registerPointerEventListener(mService.mMousePositionTracker);
+ if (displayId == DEFAULT_DISPLAY) {
+ mService.registerPointerEventListener(mService.mMousePositionTracker);
+ }
}
}
@@ -747,12 +760,16 @@
if (mUpdateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
- // TODO(multi-display): Update rotation for different displays separately.
- final int displayId = defaultDisplay.getDisplayId();
- if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) {
- mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
- } else {
- mUpdateRotation = false;
+
+ for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+ final DisplayContent displayContent = mChildren.get(displayNdx);
+ final int displayId = displayContent.getDisplayId();
+ if (displayContent.updateRotationUnchecked(false /* inTransaction */)) {
+ mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget();
+ } else if (displayId == DEFAULT_DISPLAY) {
+ // TODO(multi-display): Track rotation updates for different displays separately
+ mUpdateRotation = false;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 0c68e2c..c58212c 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -133,7 +133,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
if (!(event instanceof MotionEvent)
|| (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
return;
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index dd9ba73..42a2d9d 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -24,6 +24,7 @@
import com.android.server.wm.WindowManagerService.H;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.PointerIcon.TYPE_NOT_SPECIFIED;
import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
@@ -45,6 +46,13 @@
}
@Override
+ public void onPointerEvent(MotionEvent motionEvent, int displayId) {
+ if (displayId == getDisplayId()) {
+ onPointerEvent(motionEvent);
+ }
+ }
+
+ @Override
public void onPointerEvent(MotionEvent motionEvent) {
final int action = motionEvent.getAction();
switch (action & MotionEvent.ACTION_MASK) {
@@ -104,4 +112,8 @@
mTouchExcludeRegion.set(newRegion);
}
}
+
+ private int getDisplayId() {
+ return mDisplayContent.getDisplayId();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5db691e..83926af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -79,6 +79,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
@@ -357,6 +358,8 @@
final private KeyguardDisableHandler mKeyguardDisableHandler;
boolean mKeyguardGoingAway;
+ // VR Vr2d Display Id.
+ int mVr2dDisplayId = INVALID_DISPLAY;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -767,7 +770,7 @@
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
boolean handled = false;
try {
if (mDragState == null) {
@@ -7565,6 +7568,16 @@
accessibilityController.performComputeChangedWindowsNotLocked();
}
}
+
+ @Override
+ public void setVr2dDisplayId(int vr2dDisplayId) {
+ if (DEBUG_DISPLAY) {
+ Slog.d(TAG, "setVr2dDisplayId called for: " + vr2dDisplayId);
+ }
+ synchronized (WindowManagerService.this) {
+ mVr2dDisplayId = vr2dDisplayId;
+ }
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7decb11..7ec21a8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2043,7 +2043,7 @@
super(inputChannel, mService.mH.getLooper());
}
@Override
- public void onInputEvent(InputEvent event) {
+ public void onInputEvent(InputEvent event, int displayId) {
finishInputEvent(event, true);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 23b515e..c610ca3 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -20,6 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -631,6 +632,10 @@
return mSurfaceController;
}
+ if ((mWin.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0) {
+ windowType = SurfaceControl.WINDOW_TYPE_DONT_SCREENSHOT;
+ }
+
w.setHasSurface(false);
if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
diff --git a/services/core/jni/BroadcastRadio/Tuner.cpp b/services/core/jni/BroadcastRadio/Tuner.cpp
index f5a85c1..aa1925a 100644
--- a/services/core/jni/BroadcastRadio/Tuner.cpp
+++ b/services/core/jni/BroadcastRadio/Tuner.cpp
@@ -296,7 +296,7 @@
auto selector = convert::ProgramSelectorToHal(env, jSelector);
if (halTuner11 != nullptr) {
- convert::ThrowIfFailed(env, halTuner11->tune_1_1(selector));
+ convert::ThrowIfFailed(env, halTuner11->tuneByProgramSelector(selector));
} else {
uint32_t channel, subChannel;
if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) {
diff --git a/services/core/jni/BroadcastRadio/TunerCallback.cpp b/services/core/jni/BroadcastRadio/TunerCallback.cpp
index 04bdddf..e01d8fa 100644
--- a/services/core/jni/BroadcastRadio/TunerCallback.cpp
+++ b/services/core/jni/BroadcastRadio/TunerCallback.cpp
@@ -58,7 +58,7 @@
jmethodID handleHwFailure;
jmethodID onError;
jmethodID onConfigurationChanged;
- jmethodID onProgramInfoChanged;
+ jmethodID onCurrentProgramInfoChanged;
jmethodID onTrafficAnnouncement;
jmethodID onEmergencyAnnouncement;
jmethodID onAntennaState;
@@ -111,7 +111,7 @@
virtual Return<void> backgroundScanAvailable(bool isAvailable);
virtual Return<void> backgroundScanComplete(ProgramListResult result);
virtual Return<void> programListChanged();
- virtual Return<void> programInfoChanged();
+ virtual Return<void> currentProgramInfoChanged();
};
struct TunerCallbackContext {
@@ -192,7 +192,7 @@
mCallbackThread.enqueue([result, this](JNIEnv *env) {
if (result == Result::OK) {
- env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
} else {
TunerError cause = TunerError::CANCELLED;
if (result == Result::TIMEOUT) cause = TunerError::SCAN_TIMEOUT;
@@ -254,7 +254,7 @@
}
mCallbackThread.enqueue([this, metadata](JNIEnv *env) {
- env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
});
return Return<void>();
@@ -297,11 +297,11 @@
return Return<void>();
}
-Return<void> NativeCallback::programInfoChanged() {
+Return<void> NativeCallback::currentProgramInfoChanged() {
ALOGV("%s", __func__);
mCallbackThread.enqueue([this](JNIEnv *env) {
- env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramInfoChanged);
+ env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged);
});
return Return<void>();
@@ -379,8 +379,8 @@
gjni.TunerCallback.onError = GetMethodIDOrDie(env, tunerCbClass, "onError", "(I)V");
gjni.TunerCallback.onConfigurationChanged = GetMethodIDOrDie(env, tunerCbClass,
"onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V");
- gjni.TunerCallback.onProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
- "onProgramInfoChanged", "()V");
+ gjni.TunerCallback.onCurrentProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass,
+ "onCurrentProgramInfoChanged", "()V");
gjni.TunerCallback.onTrafficAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
"onTrafficAnnouncement", "(Z)V");
gjni.TunerCallback.onEmergencyAnnouncement = GetMethodIDOrDie(env, tunerCbClass,
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d67bf63..a1b9099 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -287,6 +287,15 @@
SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
}
+ //
+ // Default the timezone property to GMT if not set.
+ //
+ String timezoneProperty = SystemProperties.get("persist.sys.timezone");
+ if (timezoneProperty == null || timezoneProperty.isEmpty()) {
+ Slog.w(TAG, "Timezone not set; setting to GMT.");
+ SystemProperties.set("persist.sys.timezone", "GMT");
+ }
+
// If the system has "persist.sys.language" and friends set, replace them with
// "persist.sys.locale". Note that the default locale at this point is calculated
// using the "-Duser.locale" command line flag. That flag is usually populated by
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
index ceb92de..82ff0d8 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScorerAppManagerTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.when;
import android.Manifest.permission;
+import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -61,15 +62,18 @@
@RunWith(AndroidJUnit4.class)
public class NetworkScorerAppManagerTest {
+ private static final int PACKAGE_UID = 924;
private static String MOCK_SERVICE_LABEL = "Mock Service";
private static String MOCK_OVERRIDEN_SERVICE_LABEL = "Mock Service Label Override";
private static String MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID =
"Mock Network Available Notification Channel Id";
+ private static final ComponentName RECO_COMPONENT = new ComponentName("package1", "class1");
@Mock private Context mMockContext;
@Mock private PackageManager mMockPm;
@Mock private Resources mResources;
@Mock private NetworkScorerAppManager.SettingsFacade mSettingsFacade;
+ @Mock private AppOpsManager mAppOpsManager;
private NetworkScorerAppManager mNetworkScorerAppManager;
private List<ResolveInfo> mAvailableServices;
@@ -86,45 +90,69 @@
}
}), eq(PackageManager.GET_META_DATA))).thenReturn(mAvailableServices);
when(mMockContext.getResources()).thenReturn(mResources);
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+
+ mockLocationModeOn();
+ mockLocationPermissionGranted(PACKAGE_UID, RECO_COMPONENT.getPackageName());
mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext, mSettingsFacade);
}
@Test
public void testGetActiveScorer_providerAvailable() throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */);
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertEquals(MOCK_SERVICE_LABEL, activeScorer.getRecommendationServiceLabel());
}
@Test
public void testGetActiveScorer_providerAvailable_serviceLabelOverride() throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
null /* enableUseOpenWifiPackageActivityPackage*/, true /* serviceLabelOverride */);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertEquals(MOCK_OVERRIDEN_SERVICE_LABEL, activeScorer.getRecommendationServiceLabel());
}
@Test
- public void testGetActiveScorer_permissionMissing() throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksDenied(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */);
+ public void testGetActiveScorer_scoreNetworksPermissionMissing() throws Exception {
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksDenied(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
+
+ final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+ assertNull(activeScorer);
+ }
+
+ @Test
+ public void testGetActiveScorer_locationPermissionMissing() throws Exception {
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockLocationPermissionDenied(PACKAGE_UID, RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
+
+ final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+ assertNull(activeScorer);
+ }
+
+ @Test
+ public void testGetActiveScorer_locationModeOff() throws Exception {
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockLocationPermissionGranted(PACKAGE_UID, RECO_COMPONENT.getPackageName());
+ mockLocationModeOff();
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNull(activeScorer);
@@ -133,67 +161,63 @@
@Test
public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotSet()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
null /* enableUseOpenWifiPackageActivityPackage*/);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertNull(activeScorer.getEnableUseOpenWifiActivity());
}
@Test
public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityNotResolved()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
"package2" /* enableUseOpenWifiPackageActivityPackage*/);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertNull(activeScorer.getEnableUseOpenWifiActivity());
}
@Test
public void testGetActiveScorer_providerAvailable_enableUseOpenWifiActivityResolved()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertEquals(enableUseOpenWifiComponent, activeScorer.getEnableUseOpenWifiActivity());
assertNull(activeScorer.getNetworkAvailableNotificationChannelId());
}
@Test
public void testGetActiveScorer_providerAvailable_networkAvailableNotificationChannelIdSet() {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
null /* enableUseOpenWifiActivityPackage */, false /* serviceLabelOverride */,
true /* setNotificationChannelId */);
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
assertNotNull(activeScorer);
- assertEquals(recoComponent, activeScorer.getRecommendationServiceComponent());
- assertEquals(924, activeScorer.packageUid);
+ assertEquals(RECO_COMPONENT, activeScorer.getRecommendationServiceComponent());
+ assertEquals(PACKAGE_UID, activeScorer.packageUid);
assertEquals(MOCK_NETWORK_AVAILABLE_NOTIFICATION_CHANNEL_ID,
activeScorer.getNetworkAvailableNotificationChannelId());
}
@@ -209,9 +233,8 @@
@Test
public void testGetActiveScorer_packageSettingIsInvalid() throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
- setDefaultNetworkRecommendationPackage(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
+ setDefaultNetworkRecommendationPackage(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
// NETWORK_RECOMMENDATIONS_PACKAGE is set to a package that isn't a recommender.
final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
@@ -249,12 +272,12 @@
@Test
public void testSetActiveScorer_validPackage() throws Exception {
- String packageName = "package";
String newPackage = "newPackage";
- setNetworkRecoPackageSetting(packageName);
+ int newAppUid = 621;
final ComponentName recoComponent = new ComponentName(newPackage, "class1");
mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+ mockRecommendationServiceAvailable(recoComponent, newAppUid, null);
+ mockLocationPermissionGranted(newAppUid, recoComponent.getPackageName());
assertTrue(mNetworkScorerAppManager.setActiveScorer(newPackage));
verify(mSettingsFacade).putString(mMockContext,
@@ -289,11 +312,9 @@
@Test
public void testUpdateState_currentPackageValid() throws Exception {
- String packageName = "package";
- setNetworkRecoPackageSetting(packageName);
- final ComponentName recoComponent = new ComponentName(packageName, "class1");
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID , null);
mNetworkScorerAppManager.updateState();
@@ -306,11 +327,13 @@
@Test
public void testUpdateState_currentPackageNotValid_validDefault() throws Exception {
- String defaultPackage = "defaultPackage";
- setDefaultNetworkRecommendationPackage(defaultPackage);
+ final String defaultPackage = "defaultPackage";
+ final int defaultAppUid = 621;
final ComponentName recoComponent = new ComponentName(defaultPackage, "class1");
+ setDefaultNetworkRecommendationPackage(defaultPackage);
mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */, null);
+ mockRecommendationServiceAvailable(recoComponent, defaultAppUid, null);
+ mockLocationPermissionGranted(defaultAppUid, defaultPackage);
mNetworkScorerAppManager.updateState();
@@ -329,8 +352,6 @@
mNetworkScorerAppManager.updateState();
- verify(mSettingsFacade).putString(mMockContext,
- Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE, defaultPackage);
verify(mSettingsFacade).putInt(mMockContext,
Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
@@ -345,8 +366,9 @@
verify(mSettingsFacade, never()).putString(any(),
eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), anyString());
- verify(mSettingsFacade, never()).putInt(any(),
- eq(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), anyInt());
+ verify(mSettingsFacade).putInt(mMockContext,
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
}
@Test
@@ -358,8 +380,9 @@
verify(mSettingsFacade, never()).putString(any(),
eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), anyString());
- verify(mSettingsFacade, never()).putInt(any(),
- eq(Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED), anyInt());
+ verify(mSettingsFacade).putInt(mMockContext,
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
}
@Test
@@ -370,8 +393,9 @@
mNetworkScorerAppManager.updateState();
- verify(mSettingsFacade, never()).putString(any(),
- eq(Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE), any());
+ verify(mSettingsFacade).putInt(mMockContext,
+ Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED,
+ NetworkScoreManager.RECOMMENDATIONS_ENABLED_OFF);
}
@Test
@@ -415,11 +439,10 @@
@Test
public void testMigrateNetworkScorerAppSettingIfNeeded_useOpenWifiSettingIsNotEmpty()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
when(mSettingsFacade.getString(mMockContext,
@@ -441,13 +464,12 @@
@Test
public void testMigrateNetworkScorerAppSettingIfNeeded_useOpenWifiActivityNotAvail()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
// The active component doesn't have an open wifi activity so the migration shouldn't
// set USE_OPEN_WIFI_PACKAGE.
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
null /*useOpenWifiActivityPackage*/);
when(mSettingsFacade.getString(mMockContext,
Settings.Global.NETWORK_SCORER_APP))
@@ -466,11 +488,10 @@
@Test
public void testMigrateNetworkScorerAppSettingIfNeeded_packageMismatch_activity()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
// The older network scorer app setting doesn't match the new use open wifi activity package
@@ -492,18 +513,17 @@
@Test
public void testMigrateNetworkScorerAppSettingIfNeeded_packageMismatch_service()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
// The older network scorer app setting doesn't match the active package so the migration
// shouldn't set USE_OPEN_WIFI_PACKAGE.
when(mSettingsFacade.getString(mMockContext,
Settings.Global.NETWORK_SCORER_APP))
- .thenReturn(recoComponent.getPackageName() + ".diff");
+ .thenReturn(RECO_COMPONENT.getPackageName() + ".diff");
when(mSettingsFacade.getString(mMockContext,
Settings.Global.USE_OPEN_WIFI_PACKAGE)).thenReturn(null);
@@ -518,11 +538,10 @@
@Test
public void testMigrateNetworkScorerAppSettingIfNeeded_packageMatch_activity()
throws Exception {
- final ComponentName recoComponent = new ComponentName("package1", "class1");
final ComponentName enableUseOpenWifiComponent = new ComponentName("package2", "class2");
- setNetworkRecoPackageSetting(recoComponent.getPackageName());
- mockScoreNetworksGranted(recoComponent.getPackageName());
- mockRecommendationServiceAvailable(recoComponent, 924 /* packageUid */,
+ setNetworkRecoPackageSetting(RECO_COMPONENT.getPackageName());
+ mockScoreNetworksGranted(RECO_COMPONENT.getPackageName());
+ mockRecommendationServiceAvailable(RECO_COMPONENT, PACKAGE_UID /* packageUid */,
enableUseOpenWifiComponent.getPackageName());
mockEnableUseOpenWifiActivity(enableUseOpenWifiComponent);
// Old setting matches the new activity package, migration should happen.
@@ -566,6 +585,33 @@
.thenReturn(PackageManager.PERMISSION_DENIED);
}
+ private void mockLocationModeOn() {
+ mockLocationModeValue(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY);
+ }
+
+ private void mockLocationModeOff() {
+ mockLocationModeValue(Settings.Secure.LOCATION_MODE_OFF);
+ }
+
+ private void mockLocationModeValue(int returnVal) {
+ when(mSettingsFacade.getSecureInt(eq(mMockContext),
+ eq(Settings.Secure.LOCATION_MODE), anyInt())).thenReturn(returnVal);
+ }
+
+ private void mockLocationPermissionGranted(int uid, String packageName) {
+ when(mMockPm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mAppOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ }
+
+ private void mockLocationPermissionDenied(int uid, String packageName) {
+ when(mMockPm.checkPermission(permission.ACCESS_COARSE_LOCATION, packageName))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mAppOpsManager.noteOp(AppOpsManager.OP_COARSE_LOCATION, uid, packageName))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+ }
+
private void mockRecommendationServiceAvailable(final ComponentName compName, int packageUid) {
mockRecommendationServiceAvailable(compName, packageUid, null, false);
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index 856e940..91eb55b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -31,10 +31,13 @@
import org.junit.runner.RunWith;
import android.content.res.Configuration;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
import android.util.SparseIntArray;
+import android.view.MotionEvent;
import java.util.Arrays;
import java.util.LinkedList;
@@ -237,6 +240,52 @@
assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */);
}
+ /**
+ * Tests tapping on a stack in different display results in window gaining focus.
+ */
+ @Test
+ public void testInputEventBringsCorrectDisplayInFocus() throws Exception {
+ DisplayContent dc0 = sWm.getDefaultDisplayContentLocked();
+ // Create a second display
+ final DisplayContent dc1 = createNewDisplay();
+
+ // Add stack with activity.
+ final TaskStack stack0 = createTaskStackOnDisplay(dc0);
+ final Task task0 = createTaskInStack(stack0, 0 /* userId */);
+ final WindowTestUtils.TestAppWindowToken token =
+ new WindowTestUtils.TestAppWindowToken(dc0);
+ task0.addChild(token, 0);
+ dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+ sWm.registerPointerEventListener(dc0.mTapDetector);
+ final TaskStack stack1 = createTaskStackOnDisplay(dc1);
+ final Task task1 = createTaskInStack(stack1, 0 /* userId */);
+ final WindowTestUtils.TestAppWindowToken token1 =
+ new WindowTestUtils.TestAppWindowToken(dc0);
+ task1.addChild(token1, 0);
+ dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0);
+ sWm.registerPointerEventListener(dc1.mTapDetector);
+
+ // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP)
+ DisplayMetrics dm0 = dc0.getDisplayMetrics();
+ dc0.mTapDetector.onPointerEvent(
+ createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true));
+ dc0.mTapDetector.onPointerEvent(
+ createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false));
+
+ // Check focus is on primary display.
+ assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow());
+
+ // Tap on secondary display
+ DisplayMetrics dm1 = dc1.getDisplayMetrics();
+ dc1.mTapDetector.onPointerEvent(
+ createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true));
+ dc1.mTapDetector.onPointerEvent(
+ createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false));
+
+ // Check focus is on secondary.
+ assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow());
+ }
+
@Test
@Ignore
public void testFocusedWindowMultipleDisplays() throws Exception {
@@ -355,4 +404,18 @@
}
assertTrue(actualWindows.isEmpty());
}
+
+ private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) {
+ final long downTime = SystemClock.uptimeMillis();
+ final long eventTime = SystemClock.uptimeMillis() + 100;
+ final int metaState = 0;
+
+ return MotionEvent.obtain(
+ downTime,
+ eventTime,
+ isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP,
+ x,
+ y,
+ metaState);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 828d405..95adc9c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -18,6 +18,10 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -39,6 +43,7 @@
import android.os.RemoteException;
import android.view.Display;
import android.view.IWindowManager;
+import android.view.InputChannel;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
@@ -91,7 +96,14 @@
}).when(am).notifyKeyguardFlagsChanged(any());
}
- sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false,
+ InputManagerService ims = mock(InputManagerService.class);
+ // InputChannel is final and can't be mocked.
+ InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
+ if (input != null && input.length > 1) {
+ doReturn(input[1]).when(ims).monitorInput(anyString());
+ }
+
+ sWm = WindowManagerService.main(context, ims, true, false,
false, new TestWindowManagerPolicy());
}
return sWm;
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index 177759e..db49391 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -454,10 +454,6 @@
* @param conferenceableConnections The set of connections this connection can conference with.
*/
public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
- if (Objects.equals(mConferenceableConnections, conferenceableConnections)) {
- return;
- }
-
clearConferenceableList();
for (Connection c : conferenceableConnections) {
// If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8368f42..a78c261 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1368,17 +1368,6 @@
"notify_international_call_on_wfc_bool";
/**
- * Determine whether user edited tether APN (type dun) has effect
- * {@code false} - Default. APN with dun type in telephony database has no effect.
- *
- * {@code true} - DUN APN added/edited in ApnEditor will be used for tethering data call.
- *
- * @hide
- */
- public static final String KEY_EDITABLE_TETHER_APN_BOOL =
- "editable_tether_apn_bool";
-
- /**
* An array containing custom call forwarding number prefixes that will be blocked while the
* device is reporting that it is roaming. By default, there are no custom call
* forwarding prefixes and none of these numbers will be filtered. If one or more entries are
@@ -1771,7 +1760,6 @@
sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
- sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false);
sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
null);
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2e7b19a..4aae1fa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1654,8 +1654,7 @@
* @hide
*/
public String getNetworkCountryIso(int subId) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
- return getNetworkCountryIsoForPhone(phoneId);
+ return getNetworkCountryIsoForPhone(getPhoneId(subId));
}
/**
@@ -1670,7 +1669,13 @@
*/
/** {@hide} */
public String getNetworkCountryIsoForPhone(int phoneId) {
- return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return "";
+ return telephony.getNetworkCountryIsoForPhone(phoneId);
+ } catch (RemoteException ex) {
+ return "";
+ }
}
/** Network type is unknown */
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index a0e5b7b..654adb2 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -377,6 +377,13 @@
Bundle getCellLocation(String callingPkg);
/**
+ * Returns the ISO country code equivalent of the current registered
+ * operator's MCC (Mobile Country Code).
+ * @see android.telephony.TelephonyManager#getNetworkCountryIso
+ */
+ String getNetworkCountryIsoForPhone(int phoneId);
+
+ /**
* Returns the neighboring cell information of the device.
*/
List<NeighboringCellInfo> getNeighboringCellInfo(String callingPkg);
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index e4bf590..677585c 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -53,6 +53,7 @@
libtinyxml2 \
libvintf \
libhwbinder \
+ libunwindstack \
android.hidl.token@1.0
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 7e5efa1..abc0e4c 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -84,7 +84,7 @@
std::string path = file->GetSource().path;
// The name of the path has the format "<zip-file-name>@<path-to-file>".
- path = path.substr(path.find("@") + 1);
+ path = path.substr(path.find('@') + 1);
// Skip resources that are not referenced if requested.
if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e6bf3a6..3e3f432 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -915,7 +915,7 @@
bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
- const Maybe<std::string> out_text_symbols_path = {}) {
+ const Maybe<std::string>& out_text_symbols_path = {}) {
if (!options_.generate_java_class_path) {
return true;
}
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 51a75d7..a9b49d9 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -520,11 +520,10 @@
return !(*this == rhs);
}
-Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok,
- bool end)
+Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
: str_(s), separator_(sep), token_(tok), end_(end) {}
-Tokenizer::Tokenizer(StringPiece str, char sep)
+Tokenizer::Tokenizer(const StringPiece& str, char sep)
: begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
end_(str, sep, StringPiece(str.end(), 0), true) {}
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index ad3989e..f89f8bf 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -240,7 +240,7 @@
private:
friend class Tokenizer;
- iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
+ iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
android::StringPiece str_;
char separator_;
@@ -248,7 +248,7 @@
bool end_;
};
- Tokenizer(android::StringPiece str, char sep);
+ Tokenizer(const android::StringPiece& str, char sep);
iterator begin() { return begin_; }
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index a7fbc2e..91ca514 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -50,6 +50,7 @@
int testPassCount;
int testFailCount;
+ int unknownFailureCount; // unknown failure == "Process crashed", etc.
bool actionsWithNoTests;
Target(bool b, bool i, bool t, const string& p);
@@ -63,6 +64,7 @@
testActionCount(0),
testPassCount(0),
testFailCount(0),
+ unknownFailureCount(0),
actionsWithNoTests(false)
{
}
@@ -188,8 +190,13 @@
*/
void SetCurrentAction(TestAction* action);
+ bool IsSuccess();
+
+ string GetErrorMessage();
+
private:
TestAction* m_currentAction;
+ SessionStatus m_sessionStatus;
};
void
@@ -241,8 +248,10 @@
}
line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName;
print_one_line("%s", line.str().c_str());
- } else if (resultCode == -2) {
+ } else if ((resultCode == -1) || (resultCode == -2)) {
// test failed
+ // Note -2 means an assertion failure, and -1 means other exceptions. We just treat them
+ // all as "failures".
m_currentAction->failCount++;
m_currentAction->target->testFailCount++;
printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold,
@@ -257,9 +266,13 @@
}
void
-TestResults::OnSessionStatus(SessionStatus& /*status*/)
+TestResults::OnSessionStatus(SessionStatus& status)
{
//status.PrintDebugString();
+ m_sessionStatus = status;
+ if (m_currentAction && !IsSuccess()) {
+ m_currentAction->target->unknownFailureCount++;
+ }
}
void
@@ -268,6 +281,24 @@
m_currentAction = action;
}
+bool
+TestResults::IsSuccess()
+{
+ return m_sessionStatus.result_code() == -1; // Activity.RESULT_OK.
+}
+
+string
+TestResults::GetErrorMessage()
+{
+ bool found;
+ string shortMsg = get_bundle_string(m_sessionStatus.results(), &found, "shortMsg", NULL);
+ if (!found) {
+ return IsSuccess() ? "" : "Unknown failure";
+ }
+ return shortMsg;
+}
+
+
/**
* Prints the usage statement / help text.
*/
@@ -568,7 +599,7 @@
/**
* Run the build, install, and test actions.
*/
-void
+bool
run_phases(vector<Target*> targets, const Options& options)
{
int err = 0;
@@ -837,6 +868,10 @@
printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount,
g_escapeEndColor, action.failCount);
}
+ if (!testResults.IsSuccess()) {
+ printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold,
+ testResults.GetErrorMessage().c_str(), g_escapeEndColor);
+ }
}
}
@@ -907,6 +942,7 @@
}
// Tests
+ bool hasErrors = false;
if (testActions.size() > 0) {
printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
size_t maxNameLength = 0;
@@ -924,12 +960,18 @@
Target* target = targets[i];
if (target->testActionCount > 0) {
printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length());
- if (target->actionsWithNoTests) {
+ if (target->unknownFailureCount > 0) {
+ printf(" %sUnknown failure, see above message.%s\n",
+ g_escapeRedBold, g_escapeEndColor);
+ hasErrors = true;
+ } else if (target->actionsWithNoTests) {
printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold,
target->testPassCount, target->testFailCount, g_escapeEndColor);
+ hasErrors = true;
} else if (target->testFailCount > 0) {
printf(" %d passed, %s%d failed%s\n", target->testPassCount,
g_escapeRedBold, target->testFailCount, g_escapeEndColor);
+ hasErrors = true;
} else {
printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold,
target->testPassCount, g_escapeEndColor, target->testFailCount);
@@ -944,6 +986,7 @@
}
printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor);
+ return !hasErrors;
}
/**
@@ -991,7 +1034,7 @@
exit(0);
} else {
// Normal run
- run_phases(options.targets, options);
+ exit(run_phases(options.targets, options) ? 0 : 1);
}
return 0;
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
index 0aed0ce..d921783 100644
--- a/tools/locked_region_code_injection/Android.mk
+++ b/tools/locked_region_code_injection/Android.mk
@@ -9,7 +9,7 @@
asm-5.2 \
asm-commons-5.2 \
asm-tree-5.2 \
- asm-analysis-5.2
-
+ asm-analysis-5.2 \
+ guava-20.0 \
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index 9374f23..99ef8a7 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -18,6 +18,7 @@
import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.TryCatchBlockSorter;
@@ -32,6 +33,10 @@
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
+import static com.google.common.base.Preconditions.checkElementIndex;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
/**
* This visitor does two things:
*
@@ -140,10 +145,26 @@
if (operand instanceof LockTargetState) {
LockTargetState state = (LockTargetState) operand;
for (int j = 0; j < state.getTargets().size(); j++) {
+ // The instruction after a monitor_exit should be a label for the end of the implicit
+ // catch block that surrounds the synchronized block to call monitor_exit when an exception
+ // occurs.
+ checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL,
+ "Expected to find label after monitor exit");
+
+ int labelIndex = i + 1;
+ checkElementIndex(labelIndex, instructions.size());
+
+ LabelNode label = (LabelNode)instructions.get(labelIndex);
+
+ checkNotNull(handlersMap.get(i));
+ checkElementIndex(0, handlersMap.get(i).size());
+ checkState(handlersMap.get(i).get(0).end == label,
+ "Expected label to be the end of monitor exit's try block");
+
LockTarget target = state.getTargets().get(j);
MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC,
target.getPostOwner(), target.getPostMethod(), "()V", false);
- insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call);
+ insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call);
}
}
}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index 1d4f2d4..b86954d 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -228,4 +228,26 @@
Assert.assertEquals(TestTarget.unboostCount, 1);
Assert.assertEquals(TestTarget.invokeCount, 1);
}
+
+ @Test
+ public void testUnboostThatThrows() {
+ TestTarget.resetCount();
+ TestTarget t = new TestTarget();
+ boolean asserted = false;
+
+ Assert.assertEquals(TestTarget.boostCount, 0);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+
+ try {
+ t.synchronizedThrowsOnUnboost();
+ } catch (RuntimeException e) {
+ asserted = true;
+ }
+
+ Assert.assertEquals(asserted, true);
+ Assert.assertEquals(TestTarget.boostCount, 1);
+ Assert.assertEquals(TestTarget.unboostCount, 0);
+ Assert.assertEquals(TestTarget.invokeCount, 1);
+ }
+
}
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
index 8e7d478..d1c8f34 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java
@@ -17,12 +17,17 @@
public static int boostCount = 0;
public static int unboostCount = 0;
public static int invokeCount = 0;
+ public static boolean nextUnboostThrows = false;
public static void boost() {
boostCount++;
}
public static void unboost() {
+ if (nextUnboostThrows) {
+ nextUnboostThrows = false;
+ throw new RuntimeException();
+ }
unboostCount++;
}
@@ -49,4 +54,11 @@
invoke();
return this;
}
+
+ public void synchronizedThrowsOnUnboost() {
+ nextUnboostThrows = true;
+ synchronized(this) {
+ invoke();
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 7972d06..a3a1054 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -94,7 +94,7 @@
void reassociate();
- WifiInfo getConnectionInfo();
+ WifiInfo getConnectionInfo(String callingPackage);
boolean setWifiEnabled(String packageName, boolean enable);
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index 4dc7862..a367b23 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -151,8 +151,9 @@
/**
* This factor is used to adjust the rate output under the new algorithm
* such that the result is comparable to the previous algorithm.
+ * This actually converts from unit 'packets per second' to 'packets per 5 seconds'.
*/
- private static final long OUTPUT_SCALE_FACTOR = 5000;
+ private static final long OUTPUT_SCALE_FACTOR = 5;
private long mLastPacketCountUpdateTimeStamp;
/**
@@ -198,16 +199,16 @@
double currentSampleWeight = 1.0 - lastSampleWeight;
txBadRate = txBadRate * lastSampleWeight
- + (txbad - txBad) * OUTPUT_SCALE_FACTOR / timeDelta
+ + (txbad - txBad) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
* currentSampleWeight;
txSuccessRate = txSuccessRate * lastSampleWeight
- + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+ + (txgood - txSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
* currentSampleWeight;
rxSuccessRate = rxSuccessRate * lastSampleWeight
- + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR / timeDelta
+ + (rxgood - rxSuccess) * OUTPUT_SCALE_FACTOR * 1000 / timeDelta
* currentSampleWeight;
txRetriesRate = txRetriesRate * lastSampleWeight
- + (txretries - txRetries) * OUTPUT_SCALE_FACTOR / timeDelta
+ + (txretries - txRetries) * OUTPUT_SCALE_FACTOR * 1000/ timeDelta
* currentSampleWeight;
} else {
txBadRate = 0;
@@ -448,6 +449,22 @@
}
/**
+ * @hide
+ * This returns txSuccessRate in packets per second.
+ */
+ public double getTxSuccessRatePps() {
+ return txSuccessRate / OUTPUT_SCALE_FACTOR;
+ }
+
+ /**
+ * @hide
+ * This returns rxSuccessRate in packets per second.
+ */
+ public double getRxSuccessRatePps() {
+ return rxSuccessRate / OUTPUT_SCALE_FACTOR;
+ }
+
+ /**
* Record the MAC address of the WLAN interface
* @param macAddress the MAC address in {@code XX:XX:XX:XX:XX:XX} form
* @hide
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index c499edc..bc37810 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1643,7 +1643,7 @@
*/
public WifiInfo getConnectionInfo() {
try {
- return mService.getConnectionInfo();
+ return mService.getConnectionInfo(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}