Merge "Relaunch the activity with current configuration to avoid flakiness" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7005349..ea2eaa0 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,6 +18,7 @@
// Add java_aconfig_libraries to here to add them to the core framework
srcs: [
+ ":android.os.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
@@ -78,3 +79,24 @@
aconfig_declarations: "android.security.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+java_aconfig_library {
+ name: "android.security.flags-aconfig-java-host",
+ aconfig_declarations: "android.security.flags-aconfig",
+ host_supported: true,
+ test: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// OS
+aconfig_declarations {
+ name: "android.os.flags-aconfig",
+ package: "android.os",
+ srcs: ["core/java/android/os/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.os.flags-aconfig-java",
+ aconfig_declarations: "android.os.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 2a9375c..19d6f04 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -12682,7 +12682,6 @@
com.android.internal.util.LatencyTracker$Action
com.android.internal.util.LatencyTracker$ActionProperties
com.android.internal.util.LatencyTracker$FrameworkStatsLogEvent
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
com.android.internal.util.LatencyTracker$Session$$ExternalSyntheticLambda0
com.android.internal.util.LatencyTracker$Session
com.android.internal.util.LatencyTracker
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 28db61f..a040b57 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -49,7 +49,7 @@
virtual void onVmCreated(JNIEnv* env)
{
- if (mClassName.isEmpty()) {
+ if (mClassName.empty()) {
return; // Zygote. Nothing to do here.
}
@@ -66,10 +66,10 @@
* executing boot class Java code and thereby deny ourselves access to
* non-boot classes.
*/
- char* slashClassName = toSlashClassName(mClassName.string());
+ char* slashClassName = toSlashClassName(mClassName.c_str());
mClass = env->FindClass(slashClassName);
if (mClass == NULL) {
- ALOGE("ERROR: could not find class '%s'\n", mClassName.string());
+ ALOGE("ERROR: could not find class '%s'\n", mClassName.c_str());
}
free(slashClassName);
@@ -98,7 +98,7 @@
virtual void onExit(int code)
{
- if (mClassName.isEmpty()) {
+ if (mClassName.empty()) {
// if zygote
IPCThreadState::self()->stopProcess();
hardware::IPCThreadState::self()->stopProcess();
@@ -179,7 +179,7 @@
argv_String.append(argv[i]);
argv_String.append("\" ");
}
- ALOGV("app_process main with argv: %s", argv_String.string());
+ ALOGV("app_process main with argv: %s", argv_String.c_str());
}
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
@@ -282,7 +282,7 @@
}
Vector<String8> args;
- if (!className.isEmpty()) {
+ if (!className.empty()) {
// We're not in zygote mode, the only argument we need to pass
// to RuntimeInit is the application argument.
//
@@ -300,7 +300,7 @@
restOfArgs.append(argv_new[k]);
restOfArgs.append("\" ");
}
- ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());
+ ALOGV("Class name = %s, args = %s", className.c_str(), restOfArgs.c_str());
}
} else {
// We're in zygote mode.
@@ -328,13 +328,13 @@
}
}
- if (!niceName.isEmpty()) {
- runtime.setArgv0(niceName.string(), true /* setProcName */);
+ if (!niceName.empty()) {
+ runtime.setArgv0(niceName.c_str(), true /* setProcName */);
}
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
- } else if (!className.isEmpty()) {
+ } else if (!className.empty()) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 2cbd665..69c161a 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1171,7 +1171,7 @@
if (!readFile(animation.zip, "desc.txt", desString)) {
return false;
}
- char const* s = desString.string();
+ char const* s = desString.c_str();
std::string dynamicColoringPartName = "";
bool postDynamicColoring = false;
@@ -1180,7 +1180,7 @@
const char* endl = strstr(s, "\n");
if (endl == nullptr) break;
String8 line(s, endl - s);
- const char* l = line.string();
+ const char* l = line.c_str();
int fps = 0;
int width = 0;
int height = 0;
@@ -1365,7 +1365,7 @@
// If there is trimData present, override the positioning defaults.
for (Animation::Part& part : animation.parts) {
- const char* trimDataStr = part.trimData.string();
+ const char* trimDataStr = part.trimData.c_str();
for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) {
const char* endl = strstr(trimDataStr, "\n");
// No more trimData for this part.
@@ -1373,7 +1373,7 @@
break;
}
String8 line(trimDataStr, endl - trimDataStr);
- const char* lineStr = line.string();
+ const char* lineStr = line.c_str();
trimDataStr = ++endl;
int width = 0, height = 0, x = 0, y = 0;
if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) {
@@ -1606,7 +1606,7 @@
1.0f);
ALOGD("Playing files = %s/%s, Requested repeat = %d, playUntilComplete = %s",
- animation.fileName.string(), part.path.string(), part.count,
+ animation.fileName.c_str(), part.path.c_str(), part.count,
part.playUntilComplete ? "true" : "false");
// For the last animation, if we have progress indicator from
@@ -1831,17 +1831,17 @@
ATRACE_CALL();
if (mLoadedFiles.indexOf(fn) >= 0) {
SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
- fn.string());
+ fn.c_str());
return nullptr;
}
ZipFileRO *zip = ZipFileRO::open(fn);
if (zip == nullptr) {
SLOGE("Failed to open animation zip \"%s\": %s",
- fn.string(), strerror(errno));
+ fn.c_str(), strerror(errno));
return nullptr;
}
- ALOGD("%s is loaded successfully", fn.string());
+ ALOGD("%s is loaded successfully", fn.c_str());
Animation *animation = new Animation;
animation->fileName = fn;
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
index 1d78460..7807155 100644
--- a/cmds/idmap2/libidmap2/XmlParser.cpp
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -130,11 +130,14 @@
}
Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const {
+ String16 name16;
return FindAttribute(parser_, name, [&](size_t index) -> bool {
- size_t len;
- const String16 key16(parser_.getAttributeName(index, &len));
- std::string key = String8(key16).c_str();
- return key == name;
+ if (name16.empty()) {
+ name16 = String16(name.c_str(), name.size());
+ }
+ size_t key_len;
+ const auto key16 = parser_.getAttributeName(index, &key_len);
+ return key16 && name16.size() == key_len && name16 == key16;
});
}
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
index 6e0bd06..0d9f4e9 100644
--- a/cmds/incident/main.cpp
+++ b/cmds/incident/main.cpp
@@ -83,8 +83,8 @@
Status
StatusListener::onReportServiceStatus(const String16& service, int32_t status)
{
- fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status);
- ALOGD("service '%s' status %d\n", String8(service).string(), status);
+ fprintf(stderr, "service '%s' status %d\n", String8(service).c_str(), status);
+ ALOGD("service '%s' status %d\n", String8(service).c_str(), status);
return Status::ok();
}
@@ -384,7 +384,7 @@
status = service->reportIncidentToStream(args, listener, std::move(writeEnd));
if (!status.isOk()) {
- fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+ fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
return 1;
}
@@ -396,14 +396,14 @@
sp<StatusListener> listener(new StatusListener());
status = service->reportIncidentToDumpstate(std::move(writeEnd), listener);
if (!status.isOk()) {
- fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+ fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
return 1;
}
return listener->getExitCodeOrElse(stream_output(fds[0], STDOUT_FILENO));
} else {
status = service->reportIncident(args);
if (!status.isOk()) {
- fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+ fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().c_str());
return 1;
} else {
return 0;
diff --git a/cmds/incident_helper/src/TextParserBase.cpp b/cmds/incident_helper/src/TextParserBase.cpp
index e9bc70f..e625afa 100644
--- a/cmds/incident_helper/src/TextParserBase.cpp
+++ b/cmds/incident_helper/src/TextParserBase.cpp
@@ -27,11 +27,11 @@
{
string content;
if (!ReadFdToString(in, &content)) {
- fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.c_str());
return -1;
}
if (!WriteStringToFd(content, out)) {
- fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.c_str());
return -1;
}
return NO_ERROR;
@@ -42,13 +42,13 @@
{
string content;
if (!ReadFdToString(in, &content)) {
- fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.string());
+ fprintf(stderr, "[%s]Failed to read data from incidentd\n", this->name.c_str());
return -1;
}
// reverse the content
reverse(content.begin(), content.end());
if (!WriteStringToFd(content, out)) {
- fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.string());
+ fprintf(stderr, "[%s]Failed to write data to incidentd\n", this->name.c_str());
return -1;
}
return NO_ERROR;
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index ff5fd86..cc03d4a 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -101,7 +101,7 @@
fprintf(stderr, "Pasring section %d...\n", sectionID);
TextParserBase* parser = selectParser(sectionID);
if (parser != nullptr) {
- fprintf(stderr, "Running parser: %s\n", parser->name.string());
+ fprintf(stderr, "Running parser: %s\n", parser->name.c_str());
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));
diff --git a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
index ced6cf8..2a032fb 100644
--- a/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
+++ b/cmds/incident_helper/src/parsers/BatteryTypeParser.cpp
@@ -52,9 +52,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
index 43a12f6..c9bf4c5 100644
--- a/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuFreqParser.cpp
@@ -82,9 +82,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
index 5d525e6..77751a2f 100644
--- a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -130,11 +130,11 @@
record = parseRecordByColumns(line, columnIndices);
diff = record.size() - header.size();
if (diff < 0) {
- fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.c_str(), nline, -diff, line.c_str());
printRecord(record);
continue;
} else if (diff > 0) {
- fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.c_str(), nline, diff, line.c_str());
printRecord(record);
continue;
}
@@ -143,7 +143,7 @@
for (int i=0; i<(int)record.size(); i++) {
if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
}
}
proto.end(token);
@@ -155,9 +155,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
index 4fd6b06..0474a50 100644
--- a/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
+++ b/cmds/incident_helper/src/parsers/EventLogTagsParser.cpp
@@ -76,9 +76,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index 85beaf0..d16c23c 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -51,11 +51,11 @@
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());
+ fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.c_str(), nline, line.c_str());
continue;
} else if (record.size() > header.size()) {
// TODO: log this to incident report!
- fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.string(), nline, line.c_str());
+ fprintf(stderr, "[%s]Line %d has extra fields\n%s\n", this->name.c_str(), nline, line.c_str());
continue;
}
@@ -63,7 +63,7 @@
for (int i=0; i<(int)record.size(); i++) {
if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
}
}
proto.end(token);
@@ -75,9 +75,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
index 2a89c920..36710df 100644
--- a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -114,10 +114,10 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
index 4763b48..997d2e5 100644
--- a/cmds/incident_helper/src/parsers/ProcrankParser.cpp
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -60,7 +60,7 @@
if (record[record.size() - 1] == "TOTAL") { // TOTAL record
total = line;
} else {
- fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.string(), nline,
+ fprintf(stderr, "[%s]Line %d has missing fields\n%s\n", this->name.c_str(), nline,
line.c_str());
}
continue;
@@ -70,7 +70,7 @@
for (int i=0; i<(int)record.size(); i++) {
if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
}
}
proto.end(token);
@@ -104,9 +104,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/PsParser.cpp b/cmds/incident_helper/src/parsers/PsParser.cpp
index d3cb4be..55aa555 100644
--- a/cmds/incident_helper/src/parsers/PsParser.cpp
+++ b/cmds/incident_helper/src/parsers/PsParser.cpp
@@ -61,12 +61,12 @@
diff = record.size() - header.size();
if (diff < 0) {
// TODO: log this to incident report!
- fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.string(), nline, -diff, line.c_str());
+ fprintf(stderr, "[%s]Line %d has %d missing fields\n%s\n", this->name.c_str(), nline, -diff, line.c_str());
printRecord(record);
continue;
} else if (diff > 0) {
// TODO: log this to incident report!
- fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.string(), nline, diff, line.c_str());
+ fprintf(stderr, "[%s]Line %d has %d extra fields\n%s\n", this->name.c_str(), nline, diff, line.c_str());
printRecord(record);
continue;
}
@@ -75,7 +75,7 @@
for (int i=0; i<(int)record.size(); i++) {
if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
- this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ this->name.c_str(), nline, header[i].c_str(), record[i].c_str());
}
}
proto.end(token);
@@ -87,9 +87,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
index eba536b..86c34bc 100644
--- a/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
+++ b/cmds/incident_helper/src/parsers/SystemPropertiesParser.cpp
@@ -219,9 +219,9 @@
}
if (!proto.flush(out)) {
- fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.c_str());
return -1;
}
- fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.c_str(), proto.size());
return NO_ERROR;
}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 4f9059f..e70748e8 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -407,8 +407,8 @@
Status IncidentService::getIncidentReportList(const String16& pkg16, const String16& cls16,
vector<String16>* result) {
status_t err;
- const string pkg(String8(pkg16).string());
- const string cls(String8(cls16).string());
+ const string pkg(String8(pkg16).c_str());
+ const string cls(String8(cls16).c_str());
// List the reports
vector<sp<ReportFile>> all;
@@ -441,9 +441,9 @@
const String16& id16, IncidentManager::IncidentReport* result) {
status_t err;
- const string pkg(String8(pkg16).string());
- const string cls(String8(cls16).string());
- const string id(String8(id16).string());
+ const string pkg(String8(pkg16).c_str());
+ const string cls(String8(cls16).c_str());
+ const string id(String8(id16).c_str());
IncidentReportArgs args;
sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
@@ -470,9 +470,9 @@
Status IncidentService::deleteIncidentReports(const String16& pkg16, const String16& cls16,
const String16& id16) {
- const string pkg(String8(pkg16).string());
- const string cls(String8(cls16).string());
- const string id(String8(id16).string());
+ const string pkg(String8(pkg16).c_str());
+ const string cls(String8(cls16).c_str());
+ const string id(String8(id16).c_str());
sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, nullptr);
if (file != nullptr) {
@@ -484,7 +484,7 @@
}
Status IncidentService::deleteAllIncidentReports(const String16& pkg16) {
- const string pkg(String8(pkg16).string());
+ const string pkg(String8(pkg16).c_str());
mWorkDirectory->commitAll(pkg);
mBroadcaster->clearPackageBroadcasts(pkg);
@@ -572,7 +572,7 @@
while (SECTION_LIST[idx] != NULL) {
const Section* section = SECTION_LIST[idx];
if (section->id == id) {
- fprintf(out, "Section[%d] %s\n", id, section->name.string());
+ fprintf(out, "Section[%d] %s\n", id, section->name.c_str());
break;
}
idx++;
@@ -596,7 +596,7 @@
static void printPrivacy(const Privacy* p, FILE* out, String8 indent) {
if (p == NULL) return;
- fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.string(), p->field_id, p->type, p->policy);
+ fprintf(out, "%sid:%d, type:%d, dest:%d\n", indent.c_str(), p->field_id, p->type, p->policy);
if (p->children == NULL) return;
for (int i = 0; p->children[i] != NULL; i++) { // NULL-terminated.
printPrivacy(p->children[i], out, indent + " ");
@@ -609,7 +609,7 @@
const int argCount = args.size();
if (argCount >= 3) {
String8 opt = args[1];
- int sectionId = atoi(args[2].string());
+ int sectionId = atoi(args[2].c_str());
const Privacy* p = get_privacy_of_section(sectionId);
if (p == NULL) {
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 86a78f09..c9cf727 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -711,7 +711,7 @@
return NO_ERROR;
}
- ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+ ALOGD("Start incident report section %d '%s'", sectionId, section->name.c_str());
IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
// Notify listener of starting
@@ -747,7 +747,7 @@
sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
});
- ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+ ALOGD("Finish incident report section %d '%s'", sectionId, section->name.c_str());
return NO_ERROR;
}
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 581367a..c2aa269 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -60,7 +60,7 @@
const char* GZIP[] = {"/system/bin/gzip", NULL};
static pid_t fork_execute_incident_helper(const int id, Fpipe* p2cPipe, Fpipe* c2pPipe) {
- const char* ihArgs[]{INCIDENT_HELPER, "-s", String8::format("%d", id).string(), NULL};
+ const char* ihArgs[]{INCIDENT_HELPER, "-s", String8::format("%d", id).c_str(), NULL};
return fork_execute_cmd(const_cast<char**>(ihArgs), p2cPipe, c2pPipe);
}
@@ -100,7 +100,7 @@
// add O_CLOEXEC to make sure it is closed when exec incident helper
unique_fd fd(open(mFilename, O_RDONLY | O_CLOEXEC));
if (fd.get() == -1) {
- ALOGW("[%s] failed to open file", this->name.string());
+ ALOGW("[%s] failed to open file", this->name.c_str());
// There may be some devices/architectures that won't have the file.
// Just return here without an error.
return NO_ERROR;
@@ -110,13 +110,13 @@
Fpipe c2pPipe;
// initiate pipes to pass data to/from incident_helper
if (!p2cPipe.init() || !c2pPipe.init()) {
- ALOGW("[%s] failed to setup pipes", this->name.string());
+ ALOGW("[%s] failed to setup pipes", this->name.c_str());
return -errno;
}
pid_t pid = fork_execute_incident_helper(this->id, &p2cPipe, &c2pPipe);
if (pid == -1) {
- ALOGW("[%s] failed to fork", this->name.string());
+ ALOGW("[%s] failed to fork", this->name.c_str());
return -errno;
}
@@ -128,14 +128,14 @@
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
- this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+ this->name.c_str(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
kill_child(pid);
return readStatus;
}
status_t ihStatus = wait_child(pid);
if (ihStatus != NO_ERROR) {
- ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-ihStatus));
+ ALOGW("[%s] abnormal child process: %s", this->name.c_str(), strerror(-ihStatus));
return OK; // Not a fatal error.
}
@@ -169,7 +169,7 @@
index++; // look at the next file.
}
if (fd.get() == -1) {
- ALOGW("[%s] can't open all the files", this->name.string());
+ ALOGW("[%s] can't open all the files", this->name.c_str());
return NO_ERROR; // e.g. LAST_KMSG will reach here in user build.
}
FdBuffer buffer;
@@ -177,13 +177,13 @@
Fpipe c2pPipe;
// initiate pipes to pass data to/from gzip
if (!p2cPipe.init() || !c2pPipe.init()) {
- ALOGW("[%s] failed to setup pipes", this->name.string());
+ ALOGW("[%s] failed to setup pipes", this->name.c_str());
return -errno;
}
pid_t pid = fork_execute_cmd((char* const*)GZIP, &p2cPipe, &c2pPipe);
if (pid == -1) {
- ALOGW("[%s] failed to fork", this->name.string());
+ ALOGW("[%s] failed to fork", this->name.c_str());
return -errno;
}
// parent process
@@ -202,14 +202,14 @@
size_t editPos = internalBuffer->wp()->pos();
internalBuffer->wp()->move(8); // reserve 8 bytes for the varint of the data size.
size_t dataBeginAt = internalBuffer->wp()->pos();
- VLOG("[%s] editPos=%zu, dataBeginAt=%zu", this->name.string(), editPos, dataBeginAt);
+ VLOG("[%s] editPos=%zu, dataBeginAt=%zu", this->name.c_str(), editPos, dataBeginAt);
status_t readStatus = buffer.readProcessedDataInStream(
fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs,
isSysfs(mFilenames[index]));
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
- ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.string(),
+ ALOGW("[%s] failed to read data from gzip: %s, timedout: %s", this->name.c_str(),
strerror(-readStatus), buffer.timedOut() ? "true" : "false");
kill_child(pid);
return readStatus;
@@ -217,7 +217,7 @@
status_t gzipStatus = wait_child(pid);
if (gzipStatus != NO_ERROR) {
- ALOGW("[%s] abnormal child process: %s", this->name.string(), strerror(-gzipStatus));
+ ALOGW("[%s] abnormal child process: %s", this->name.c_str(), strerror(-gzipStatus));
return gzipStatus;
}
// Revisit the actual size from gzip result and edit the internal buffer accordingly.
@@ -290,7 +290,7 @@
FdBuffer buffer;
err = buffer.read(data->pipe.readFd().get(), this->timeoutMs);
if (err != NO_ERROR) {
- ALOGE("[%s] reader failed with error '%s'", this->name.string(), strerror(-err));
+ ALOGE("[%s] reader failed with error '%s'", this->name.c_str(), strerror(-err));
}
// If the worker side is finished, then return its error (which may overwrite
@@ -300,7 +300,7 @@
data->pipe.close();
if (data->workerError != NO_ERROR) {
err = data->workerError;
- ALOGE("[%s] worker failed with error '%s'", this->name.string(), strerror(-err));
+ ALOGE("[%s] worker failed with error '%s'", this->name.c_str(), strerror(-err));
}
workerDone = data->workerDone;
}
@@ -309,17 +309,17 @@
if (err != NO_ERROR) {
char errMsg[128];
snprintf(errMsg, 128, "[%s] failed with error '%s'",
- this->name.string(), strerror(-err));
+ this->name.c_str(), strerror(-err));
writer->error(this, err, "WorkerThreadSection failed.");
return NO_ERROR;
}
if (buffer.truncated()) {
- ALOGW("[%s] too large, truncating", this->name.string());
+ ALOGW("[%s] too large, truncating", this->name.c_str());
// Do not write a truncated section. It won't pass through the PrivacyFilter.
return NO_ERROR;
}
if (!workerDone || buffer.timedOut()) {
- ALOGW("[%s] timed out", this->name.string());
+ ALOGW("[%s] timed out", this->name.c_str());
return NO_ERROR;
}
@@ -360,18 +360,18 @@
Fpipe ihPipe;
if (!cmdPipe.init() || !ihPipe.init()) {
- ALOGW("[%s] failed to setup pipes", this->name.string());
+ ALOGW("[%s] failed to setup pipes", this->name.c_str());
return -errno;
}
pid_t cmdPid = fork_execute_cmd((char* const*)mCommand, NULL, &cmdPipe);
if (cmdPid == -1) {
- ALOGW("[%s] failed to fork", this->name.string());
+ ALOGW("[%s] failed to fork", this->name.c_str());
return -errno;
}
pid_t ihPid = fork_execute_incident_helper(this->id, &cmdPipe, &ihPipe);
if (ihPid == -1) {
- ALOGW("[%s] failed to fork", this->name.string());
+ ALOGW("[%s] failed to fork", this->name.c_str());
return -errno;
}
@@ -381,7 +381,7 @@
writer->setSectionStats(buffer);
if (readStatus != NO_ERROR || buffer.timedOut()) {
ALOGW("[%s] failed to read data from incident helper: %s, timedout: %s",
- this->name.string(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
+ this->name.c_str(), strerror(-readStatus), buffer.timedOut() ? "true" : "false");
kill_child(cmdPid);
kill_child(ihPid);
return readStatus;
@@ -393,7 +393,7 @@
status_t ihStatus = wait_child(ihPid);
if (cmdStatus != NO_ERROR || ihStatus != NO_ERROR) {
ALOGW("[%s] abnormal child processes, return status: command: %s, incident helper: %s",
- this->name.string(), strerror(-cmdStatus), strerror(-ihStatus));
+ this->name.c_str(), strerror(-cmdStatus), strerror(-ihStatus));
// Not a fatal error.
return NO_ERROR;
}
@@ -428,7 +428,7 @@
sp<IBinder> service = defaultServiceManager()->checkService(mService);
if (service == NULL) {
- ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string());
+ ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).c_str());
return NAME_NOT_FOUND;
}
@@ -463,14 +463,14 @@
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
if (service == NULL) {
- ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).string());
+ ALOGW("TextDumpsysSection: Can't lookup service: %s", String8(mService).c_str());
return NAME_NOT_FOUND;
}
// Create pipe
Fpipe dumpPipe;
if (!dumpPipe.init()) {
- ALOGW("[%s] failed to setup pipe", this->name.string());
+ ALOGW("[%s] failed to setup pipe", this->name.c_str());
return -errno;
}
@@ -482,7 +482,7 @@
signal(SIGPIPE, sigpipe_handler);
status_t err = service->dump(write_fd.get(), this->mArgs);
if (err != OK) {
- ALOGW("[%s] dump thread failed. Error: %s", this->name.string(), strerror(-err));
+ ALOGW("[%s] dump thread failed. Error: %s", this->name.c_str(), strerror(-err));
}
write_fd.reset();
});
@@ -490,7 +490,7 @@
// Collect dump content
FdBuffer buffer;
ProtoOutputStream proto;
- proto.write(TextDumpProto::COMMAND, std::string(name.string()));
+ proto.write(TextDumpProto::COMMAND, std::string(name.c_str()));
proto.write(TextDumpProto::DUMP_DURATION_NS, int64_t(Nanotime() - start));
buffer.write(proto.data());
@@ -504,7 +504,7 @@
dumpPipe.readFd().reset();
writer->setSectionStats(buffer);
if (readStatus != OK || buffer.timedOut()) {
- ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.string(),
+ ALOGW("[%s] failed to read from dumpsys: %s, timedout: %s", this->name.c_str(),
strerror(-readStatus), buffer.timedOut() ? "true" : "false");
worker.detach();
return readStatus;
@@ -579,7 +579,7 @@
// Hence forking a new process to prevent memory fragmentation.
pid_t pid = fork();
if (pid < 0) {
- ALOGW("[%s] failed to fork", this->name.string());
+ ALOGW("[%s] failed to fork", this->name.c_str());
return errno;
}
if (pid > 0) {
@@ -593,7 +593,7 @@
android_logger_list_free);
if (android_logger_open(loggers.get(), mLogID) == NULL) {
- ALOGE("[%s] Can't get logger.", this->name.string());
+ ALOGE("[%s] Can't get logger.", this->name.c_str());
_exit(EXIT_FAILURE);
}
@@ -610,7 +610,7 @@
// status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end.
if (status <= 0) {
if (status != -EAGAIN) {
- ALOGW("[%s] fails to read a log_msg.\n", this->name.string());
+ ALOGW("[%s] fails to read a log_msg.\n", this->name.c_str());
err = -status;
}
break;
@@ -680,7 +680,7 @@
AndroidLogEntry entry;
status = android_log_processLogBuffer(&msg.entry, &entry);
if (status != OK) {
- ALOGW("[%s] fails to process to an entry.\n", this->name.string());
+ ALOGW("[%s] fails to process to an entry.\n", this->name.c_str());
err = status;
break;
}
@@ -702,7 +702,7 @@
}
if (!proto.flush(pipeWriteFd.get())) {
if (errno == EPIPE) {
- ALOGW("[%s] wrote to a broken pipe\n", this->name.string());
+ ALOGW("[%s] wrote to a broken pipe\n", this->name.c_str());
}
err = errno;
break;
@@ -757,7 +757,7 @@
}
ssize_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN);
if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) {
- ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno));
+ ALOGE("[%s] Can't read '%s': %s", name.c_str(), link_name, strerror(errno));
continue;
}
// readlink(2) does not put a null terminator at the end
@@ -788,7 +788,7 @@
Fpipe dumpPipe;
if (!dumpPipe.init()) {
- ALOGW("[%s] failed to setup dump pipe", this->name.string());
+ ALOGW("[%s] failed to setup dump pipe", this->name.c_str());
err = -errno;
break;
}
@@ -822,12 +822,12 @@
// Wait on the child to avoid it becoming a zombie process.
status_t cStatus = wait_child(child);
if (err != NO_ERROR) {
- ALOGW("[%s] failed to read stack dump: %d", this->name.string(), err);
+ ALOGW("[%s] failed to read stack dump: %d", this->name.c_str(), err);
dumpPipe.readFd().reset();
break;
}
if (cStatus != NO_ERROR) {
- ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus));
+ ALOGE("[%s] child had an issue: %s\n", this->name.c_str(), strerror(-cStatus));
}
// Resize dump buffer
@@ -852,7 +852,7 @@
dumpPipe.readFd().reset();
if (!proto.flush(pipeWriteFd.get())) {
if (errno == EPIPE) {
- ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
+ ALOGE("[%s] wrote to a broken pipe\n", this->name.c_str());
}
err = errno;
break;
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index 7d20a74..6b2fb8e 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -62,8 +62,8 @@
continue;
}
String8 filename = dirbase + entry->d_name;
- if (stat(filename.string(), &st) != 0) {
- ALOGE("Unable to stat file %s", filename.string());
+ if (stat(filename.c_str(), &st) != 0) {
+ ALOGE("Unable to stat file %s", filename.c_str());
continue;
}
if (!S_ISREG(st.st_mode)) {
@@ -88,7 +88,7 @@
// Remove files until we're under our limits.
for (std::vector<std::pair<String8, struct stat>>::iterator it = files.begin();
it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) {
- remove(it->first.string());
+ remove(it->first.c_str());
totalSize -= it->second.st_size;
totalCount--;
}
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 214b12c..0351a00 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -12713,7 +12713,6 @@
com.android.internal.util.LatencyTracker$Action
com.android.internal.util.LatencyTracker$ActionProperties
com.android.internal.util.LatencyTracker$FrameworkStatsLogEvent
-com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
com.android.internal.util.LatencyTracker$Session$$ExternalSyntheticLambda0
com.android.internal.util.LatencyTracker$Session
com.android.internal.util.LatencyTracker
diff --git a/core/api/current.txt b/core/api/current.txt
index d26cf2e..e003624 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -39351,8 +39351,10 @@
}
public final class FileIntegrityManager {
+ method @FlaggedApi(Flags.FLAG_FSVERITY_API) @Nullable public byte[] getFsverityDigest(@NonNull java.io.File) throws java.io.IOException;
method public boolean isApkVeritySupported();
method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public boolean isAppSourceCertificateTrusted(@NonNull java.security.cert.X509Certificate) throws java.security.cert.CertificateEncodingException;
+ method @FlaggedApi(Flags.FLAG_FSVERITY_API) public void setupFsverity(@NonNull java.io.File) throws java.io.IOException;
}
public final class KeyChain {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b1feb41..7f1e289 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,7 +6,9 @@
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
+ field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+ field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2137f47..adbd06c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3793,6 +3793,10 @@
field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
}
+ public class PackageArchiver {
+ method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+ }
+
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
@@ -3890,6 +3894,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+ method @NonNull public android.content.pm.PackageArchiver getPackageArchiver();
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
@@ -13257,6 +13262,48 @@
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
+ public final class HotwordTrainingAudio implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.media.AudioFormat getAudioFormat();
+ method @NonNull public int getAudioType();
+ method @NonNull public byte[] getHotwordAudio();
+ method public int getHotwordOffsetMillis();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingAudio> CREATOR;
+ field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
+ }
+
+ public static final class HotwordTrainingAudio.Builder {
+ ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
+ method @NonNull public android.service.voice.HotwordTrainingAudio build();
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioType(@NonNull int);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(@NonNull byte...);
+ method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
+ }
+
+ public final class HotwordTrainingData implements android.os.Parcelable {
+ method public int describeContents();
+ method public static int getMaxTrainingDataSize();
+ method public int getTimeoutStage();
+ method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudios();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
+ field public static final int TIMEOUT_STAGE_EARLY = 2; // 0x2
+ field public static final int TIMEOUT_STAGE_LATE = 4; // 0x4
+ field public static final int TIMEOUT_STAGE_MIDDLE = 3; // 0x3
+ field public static final int TIMEOUT_STAGE_UNKNOWN = 0; // 0x0
+ field public static final int TIMEOUT_STAGE_VERY_EARLY = 1; // 0x1
+ }
+
+ public static final class HotwordTrainingData.Builder {
+ ctor public HotwordTrainingData.Builder();
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
+ method @NonNull public android.service.voice.HotwordTrainingData build();
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder setTimeoutStage(int);
+ method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudios(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>);
+ }
+
public interface SandboxedDetectionInitializer {
method public static int getMaxCustomInitializationStatus();
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d43d785..22d2999 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -28,6 +28,7 @@
field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES";
field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS";
+ field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE";
@@ -54,6 +55,7 @@
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+ field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH";
field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 021f932..32c40df 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -35,6 +35,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -1224,4 +1225,13 @@
*/
@NonNull
public abstract StatsEvent getCachedAppsHighWatermarkStats(int atomTag, boolean resetAfterPull);
+
+ /**
+ * Internal method for clearing app data, with the extra param that is used to indicate restore.
+ * Used by Backup service during restore operation.
+ *
+ * @hide
+ */
+ public abstract boolean clearApplicationUserData(String packageName, boolean keepState,
+ boolean isRestore, IPackageDataObserver observer, int userId);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1665cc1..39589fa 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -29,6 +29,8 @@
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
@@ -206,6 +208,7 @@
import android.window.WindowProviderService;
import android.window.WindowTokenClientController;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
@@ -6112,9 +6115,21 @@
final boolean shouldUpdateResources = hasPublicResConfigChange
|| shouldUpdateResources(activityToken, currentResConfig, newConfig,
amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
- final boolean shouldReportChange = shouldReportChange(
- activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
- activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
+
+ // TODO(b/274944389): remove once a longer-term solution is implemented.
+ boolean skipActivityRelaunchWhenDocking = activity.getResources().getBoolean(
+ R.bool.config_skipActivityRelaunchWhenDocking);
+ int handledConfigChanges = activity.mActivityInfo.getRealConfigChanged();
+ if (skipActivityRelaunchWhenDocking && onlyDeskInUiModeChanged(activity.mCurrentConfig,
+ newConfig)) {
+ // If we're not relaunching this activity when docking, we should send the configuration
+ // changed event. Pretend as if the activity is handling uiMode config changes in its
+ // manifest so that we'll report any dock changes.
+ handledConfigChanges |= ActivityInfo.CONFIG_UI_MODE;
+ }
+
+ final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
+ r.mSizeConfigurations, handledConfigChanges, alwaysReportChange);
// Nothing significant, don't proceed with updating and reporting.
if (!shouldUpdateResources && !shouldReportChange) {
return null;
@@ -6161,6 +6176,25 @@
}
/**
+ * Returns true if the uiMode configuration changed, and desk mode
+ * ({@link android.content.res.Configuration#UI_MODE_TYPE_DESK}) was the only change to uiMode.
+ */
+ private boolean onlyDeskInUiModeChanged(Configuration oldConfig, Configuration newConfig) {
+ boolean deskModeChanged = isInDeskUiMode(oldConfig) != isInDeskUiMode(newConfig);
+
+ // UI mode contains fields other than the UI mode type, so determine if any other fields
+ // changed.
+ boolean uiModeOtherFieldsChanged =
+ (oldConfig.uiMode & ~UI_MODE_TYPE_MASK) != (newConfig.uiMode & ~UI_MODE_TYPE_MASK);
+
+ return deskModeChanged && !uiModeOtherFieldsChanged;
+ }
+
+ private static boolean isInDeskUiMode(Configuration config) {
+ return (config.uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_DESK;
+ }
+
+ /**
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.
*
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0255860..fcd13b8 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -64,6 +64,7 @@
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ModuleInfo;
+import android.content.pm.PackageArchiver;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageItemInfo;
@@ -172,6 +173,7 @@
private volatile UserManager mUserManager;
private volatile PermissionManager mPermissionManager;
private volatile PackageInstaller mInstaller;
+ private volatile PackageArchiver mPackageArchiver;
private volatile ArtManager mArtManager;
private volatile DevicePolicyManager mDevicePolicyManager;
private volatile String mPermissionsControllerPackageName;
@@ -3282,6 +3284,18 @@
}
@Override
+ public PackageArchiver getPackageArchiver() {
+ if (mPackageArchiver == null) {
+ try {
+ mPackageArchiver = new PackageArchiver(mContext, mPM.getPackageArchiverService());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return mPackageArchiver;
+ }
+
+ @Override
public boolean isPackageAvailable(String packageName) {
try {
return mPM.isPackageAvailable(packageName, getUserId());
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index afeb3d29..31f6418 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6705,6 +6705,15 @@
public static final String EXTRA_VISIBILITY_ALLOW_LIST =
"android.intent.extra.VISIBILITY_ALLOW_LIST";
+ /**
+ * A boolean extra used with {@link #ACTION_PACKAGE_DATA_CLEARED} which indicates if the intent
+ * is broadcast as part of a restore operation.
+ *
+ * @hide
+ */
+ public static final String EXTRA_IS_RESTORE =
+ "android.intent.extra.IS_RESTORE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl
new file mode 100644
index 0000000..b34b708
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.content.pm.SigningDetails;
+
+/**
+ * Contains fields required for archived package installation,
+ * i.e. installation without an APK.
+ * @hide
+ */
+parcelable ArchivedPackageParcel {
+ String packageName;
+ SigningDetails signingDetails;
+ int versionCode;
+ int versionCodeMajor;
+ int targetSdkVersion;
+ boolean clearUserDataAllowed;
+ boolean backupAllowed;
+ boolean defaultToDeviceProtectedStorage;
+ boolean requestLegacyExternalStorage;
+ boolean userDataFragile;
+ boolean clearUserDataOnFailedRestoreAllowed;
+}
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
new file mode 100644
index 0000000..fc471c4
--- /dev/null
+++ b/core/java/android/content/pm/IPackageArchiverService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+import android.content.IntentSender;
+import android.os.UserHandle;
+
+/** {@hide} */
+interface IPackageArchiverService {
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
+ void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index ea0f5ff..916c249 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -22,9 +22,11 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.ChangedPackages;
import android.content.pm.InstantAppInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.InstallSourceInfo;
import android.content.pm.IOnChecksumsReadyListener;
@@ -650,6 +652,8 @@
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
IPackageInstaller getPackageInstaller();
+ IPackageArchiverService getPackageArchiverService();
+
@EnforcePermission("DELETE_PACKAGES")
boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
@UnsupportedAppUsage
@@ -830,4 +834,6 @@
void registerPackageMonitorCallback(IRemoteCallback callback, int userId);
void unregisterPackageMonitorCallback(IRemoteCallback callback);
+
+ ArchivedPackageParcel getArchivedPackage(in String apkPath);
}
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
new file mode 100644
index 0000000..d739d50
--- /dev/null
+++ b/core/java/android/content/pm/PackageArchiver.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.IntentSender;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.ParcelableException;
+import android.os.RemoteException;
+
+/**
+ * {@code ArchiveManager} is used to archive apps. During the archival process, the apps APKs and
+ * cache are removed from the device while the user data is kept. Through the
+ * {@code requestUnarchive()} call, apps can be restored again through their responsible app store.
+ *
+ * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
+ * will be displayed to users with UI treatment to highlight that said apps are archived. If
+ * a user taps on an archived app, the app will be unarchived and the restoration process is
+ * communicated.
+ *
+ * @hide
+ */
+// TODO(b/278560219) Improve public documentation.
+@SystemApi
+public class PackageArchiver {
+
+ private final Context mContext;
+ private final IPackageArchiverService mService;
+
+ /**
+ * @hide
+ */
+ public PackageArchiver(Context context, IPackageArchiverService service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Requests to archive a package which is currently installed.
+ *
+ * @param statusReceiver Callback used to notify when the operation is completed.
+ * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
+ * caller.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.DELETE_PACKAGES,
+ Manifest.permission.REQUEST_DELETE_PACKAGES})
+ @SystemApi
+ public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+ throws NameNotFoundException {
+ try {
+ mService.requestArchive(packageName, mContext.getPackageName(), statusReceiver,
+ mContext.getUser());
+ } catch (ParcelableException e) {
+ e.maybeRethrow(NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 885e67e1..c384389 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1682,6 +1682,13 @@
public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26;
/**
+ * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that this
+ * session is for archived package installation.
+ * @hide
+ */
+ public static final int INSTALL_ARCHIVED = 1 << 27;
+
+ /**
* Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
* a development-only feature and should not be used on end user devices.
*
@@ -9934,6 +9941,16 @@
public abstract @NonNull PackageInstaller getPackageInstaller();
/**
+ * {@link PackageArchiver} can be used to archive and restore archived packages.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull PackageArchiver getPackageArchiver() {
+ throw new UnsupportedOperationException(
+ "getPackageArchiver not implemented in subclass");
+ }
+ /**
* Adds a {@code CrossProfileIntentFilter}. After calling this method all
* intents sent from the user with id sourceUserId can also be be resolved
* by activities in the user with id targetUserId if they match the
diff --git a/core/java/android/content/pm/SigningDetails.aidl b/core/java/android/content/pm/SigningDetails.aidl
new file mode 100644
index 0000000..95f3ca7
--- /dev/null
+++ b/core/java/android/content/pm/SigningDetails.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.pm;
+
+parcelable SigningDetails;
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 269bec2..71cfd1b 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
@@ -138,6 +139,39 @@
*/
private final boolean mIsSdkLibrary;
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ private final boolean mClearUserDataAllowed;
+
+ /**
+ * Set to <code>false</code> if the application does not wish to permit any OS-driven
+ * backups of its data; <code>true</code> otherwise.
+ */
+ private final boolean mBackupAllowed;
+
+ /**
+ * When set, the default data storage directory for this app is pointed at
+ * the device-protected location.
+ */
+ private final boolean mDefaultToDeviceProtectedStorage;
+
+ /**
+ * If {@code true} this app requests full external storage access.
+ */
+ private final boolean mRequestLegacyExternalStorage;
+
+ /**
+ * Indicates whether this application has declared its user data as fragile, causing the
+ * system to prompt the user on whether to keep the user data on uninstall.
+ */
+ private final boolean mUserDataFragile;
+
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ private final boolean mClearUserDataOnFailedRestoreAllowed;
+
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
int versionCodeMajor, int revisionCode, int installLocation,
@@ -148,7 +182,10 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed,
+ boolean backupAllowed, boolean defaultToDeviceProtectedStorage,
+ boolean requestLegacyExternalStorage, boolean userDataFragile,
+ boolean clearUserDataOnFailedRestoreAllowed) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -182,6 +219,54 @@
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
+ mClearUserDataAllowed = clearUserDataAllowed;
+ mBackupAllowed = backupAllowed;
+ mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage;
+ mRequestLegacyExternalStorage = requestLegacyExternalStorage;
+ mUserDataFragile = userDataFragile;
+ mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed;
+ }
+
+ public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
+ mPath = path;
+ mPackageName = archivedPackage.packageName;
+ mSplitName = null; // base.apk
+ mSplitTypes = null;
+ mFeatureSplit = false;
+ mConfigForSplit = null;
+ mUsesSplitName = null;
+ mRequiredSplitTypes = null;
+ mSplitRequired = hasAnyRequiredSplitTypes();
+ mVersionCode = archivedPackage.versionCode;
+ mVersionCodeMajor = archivedPackage.versionCodeMajor;
+ mRevisionCode = 0;
+ mInstallLocation = PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
+ mVerifiers = new VerifierInfo[]{};
+ mSigningDetails = archivedPackage.signingDetails;
+ mCoreApp = false;
+ mDebuggable = false;
+ mProfileableByShell = false;
+ mMultiArch = false;
+ mUse32bitAbi = false;
+ mUseEmbeddedDex = false;
+ mExtractNativeLibs = false;
+ mIsolatedSplits = false;
+ mTargetPackageName = null;
+ mOverlayIsStatic = false;
+ mOverlayPriority = 0;
+ mRequiredSystemPropertyName = null;
+ mRequiredSystemPropertyValue = null;
+ mMinSdkVersion = ApkLiteParseUtils.DEFAULT_MIN_SDK_VERSION;
+ mTargetSdkVersion = archivedPackage.targetSdkVersion;
+ mRollbackDataPolicy = 0;
+ mHasDeviceAdminReceiver = false;
+ mIsSdkLibrary = false;
+ mClearUserDataAllowed = archivedPackage.clearUserDataAllowed;
+ mBackupAllowed = archivedPackage.backupAllowed;
+ mDefaultToDeviceProtectedStorage = archivedPackage.defaultToDeviceProtectedStorage;
+ mRequestLegacyExternalStorage = archivedPackage.requestLegacyExternalStorage;
+ mUserDataFragile = archivedPackage.userDataFragile;
+ mClearUserDataOnFailedRestoreAllowed = archivedPackage.clearUserDataOnFailedRestoreAllowed;
}
/**
@@ -474,6 +559,9 @@
return mRollbackDataPolicy;
}
+ /**
+ * Indicates if this app contains a {@link android.app.admin.DeviceAdminReceiver}.
+ */
@DataClass.Generated.Member
public boolean isHasDeviceAdminReceiver() {
return mHasDeviceAdminReceiver;
@@ -487,11 +575,62 @@
return mIsSdkLibrary;
}
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ @DataClass.Generated.Member
+ public boolean isClearUserDataAllowed() {
+ return mClearUserDataAllowed;
+ }
+
+ /**
+ * Set to <code>false</code> if the application does not wish to permit any OS-driven
+ * backups of its data; <code>true</code> otherwise.
+ */
+ @DataClass.Generated.Member
+ public boolean isBackupAllowed() {
+ return mBackupAllowed;
+ }
+
+ /**
+ * When set, the default data storage directory for this app is pointed at
+ * the device-protected location.
+ */
+ @DataClass.Generated.Member
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return mDefaultToDeviceProtectedStorage;
+ }
+
+ /**
+ * If {@code true} this app requests full external storage access.
+ */
+ @DataClass.Generated.Member
+ public boolean isRequestLegacyExternalStorage() {
+ return mRequestLegacyExternalStorage;
+ }
+
+ /**
+ * Indicates whether this application has declared its user data as fragile, causing the
+ * system to prompt the user on whether to keep the user data on uninstall.
+ */
+ @DataClass.Generated.Member
+ public boolean isUserDataFragile() {
+ return mUserDataFragile;
+ }
+
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ @DataClass.Generated.Member
+ public boolean isClearUserDataOnFailedRestoreAllowed() {
+ return mClearUserDataOnFailedRestoreAllowed;
+ }
+
@DataClass.Generated(
- time = 1643063342990L,
+ time = 1693422809896L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mClearUserDataAllowed\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 4f6bcb6..066ff689 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -40,6 +40,7 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -73,7 +74,7 @@
// Constants copied from services.jar side since they're not accessible
private static final String ANDROID_RES_NAMESPACE =
"http://schemas.android.com/apk/res/android";
- private static final int DEFAULT_MIN_SDK_VERSION = 1;
+ public static final int DEFAULT_MIN_SDK_VERSION = 1;
private static final int DEFAULT_TARGET_SDK_VERSION = 0;
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
@@ -446,6 +447,13 @@
int overlayPriority = 0;
int rollbackDataPolicy = 0;
+ boolean clearUserDataAllowed = true;
+ boolean backupAllowed = true;
+ boolean defaultToDeviceProtectedStorage = false;
+ String requestLegacyExternalStorage = null;
+ boolean userDataFragile = false;
+ boolean clearUserDataOnFailedRestoreAllowed = true;
+
String requiredSystemPropertyName = null;
String requiredSystemPropertyValue = null;
@@ -484,6 +492,23 @@
"extractNativeLibs", true);
useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"useEmbeddedDex", false);
+
+ clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "allowClearUserDataOnFailedRestore", true);
+ backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "allowBackup", true);
+ defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue(
+ ANDROID_RES_NAMESPACE,
+ "defaultToDeviceProtectedStorage", false);
+ userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
+ "hasFragileUserData", false);
+ clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue(
+ ANDROID_RES_NAMESPACE,
+ "allowClearUserDataOnFailedRestore", true);
+
+ requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
+ "requestLegacyExternalStorage");
+
rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
"rollbackDataPolicy", 0);
String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
@@ -604,6 +629,9 @@
return input.skip(message);
}
+ boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
+ requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q);
+
return input.success(
new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
configForSplit, usesSplitName, isSplitRequired, versionCode,
@@ -613,7 +641,9 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary));
+ hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed,
+ defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage,
+ userDataFragile, clearUserDataOnFailedRestoreAllowed));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index e2789c9..4638af7 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.PackageInfo;
+import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
import com.android.internal.util.ArrayUtils;
@@ -78,6 +79,8 @@
private final int mInstallLocation;
/** Information about a package verifiers as used during package verification */
private final @NonNull VerifierInfo[] mVerifiers;
+ /** Signing-related data of an application package */
+ private final @NonNull SigningDetails mSigningDetails;
/** Indicate whether any split APKs that are features. Ordered by splitName */
private final @Nullable boolean[] mIsFeatureSplits;
@@ -109,6 +112,33 @@
* Indicates if this package is a sdk.
*/
private final boolean mIsSdkLibrary;
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ private final boolean mClearUserDataAllowed;
+ /**
+ * Set to <code>false</code> if the application does not wish to permit any OS-driven
+ * backups of its data; <code>true</code> otherwise.
+ */
+ private final boolean mBackupAllowed;
+ /**
+ * When set, the default data storage directory for this app is pointed at
+ * the device-protected location.
+ */
+ private final boolean mDefaultToDeviceProtectedStorage;
+ /**
+ * If {@code true} this app requests full external storage access.
+ */
+ private final boolean mRequestLegacyExternalStorage;
+ /**
+ * Indicates whether this application has declared its user data as fragile, causing the
+ * system to prompt the user on whether to keep the user data on uninstall.
+ */
+ private final boolean mUserDataFragile;
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ private final boolean mClearUserDataOnFailedRestoreAllowed;
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -123,6 +153,7 @@
mVersionCodeMajor = baseApk.getVersionCodeMajor();
mInstallLocation = baseApk.getInstallLocation();
mVerifiers = baseApk.getVerifiers();
+ mSigningDetails = baseApk.getSigningDetails();
mBaseRevisionCode = baseApk.getRevisionCode();
mCoreApp = baseApk.isCoreApp();
mDebuggable = baseApk.isDebuggable();
@@ -144,6 +175,12 @@
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
+ mClearUserDataAllowed = baseApk.isClearUserDataAllowed();
+ mBackupAllowed = baseApk.isBackupAllowed();
+ mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage();
+ mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage();
+ mUserDataFragile = baseApk.isUserDataFragile();
+ mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed();
}
/**
@@ -325,6 +362,14 @@
}
/**
+ * Signing-related data of an application package
+ */
+ @DataClass.Generated.Member
+ public @NonNull SigningDetails getSigningDetails() {
+ return mSigningDetails;
+ }
+
+ /**
* Indicate whether any split APKs that are features. Ordered by splitName
*/
@DataClass.Generated.Member
@@ -414,12 +459,62 @@
return mIsSdkLibrary;
}
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ @DataClass.Generated.Member
+ public boolean isClearUserDataAllowed() {
+ return mClearUserDataAllowed;
+ }
+
+ /**
+ * Set to <code>false</code> if the application does not wish to permit any OS-driven
+ * backups of its data; <code>true</code> otherwise.
+ */
+ @DataClass.Generated.Member
+ public boolean isBackupAllowed() {
+ return mBackupAllowed;
+ }
+
+ /**
+ * When set, the default data storage directory for this app is pointed at
+ * the device-protected location.
+ */
+ @DataClass.Generated.Member
+ public boolean isDefaultToDeviceProtectedStorage() {
+ return mDefaultToDeviceProtectedStorage;
+ }
+
+ /**
+ * If {@code true} this app requests full external storage access.
+ */
+ @DataClass.Generated.Member
+ public boolean isRequestLegacyExternalStorage() {
+ return mRequestLegacyExternalStorage;
+ }
+
+ /**
+ * Indicates whether this application has declared its user data as fragile, causing the
+ * system to prompt the user on whether to keep the user data on uninstall.
+ */
+ @DataClass.Generated.Member
+ public boolean isUserDataFragile() {
+ return mUserDataFragile;
+ }
+
+ /**
+ * Indicates whether this application's data will be cleared on a failed restore.
+ */
+ @DataClass.Generated.Member
+ public boolean isClearUserDataOnFailedRestoreAllowed() {
+ return mClearUserDataOnFailedRestoreAllowed;
+ }
+
@DataClass.Generated(
- time = 1643132127068L,
+ time = 1693423910860L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures =
- "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mClearUserDataAllowed\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 76b29e6..5cc3b92 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,7 +27,6 @@
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
-import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -430,49 +429,35 @@
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
String[] availableLocales;
- if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
- // The LocaleList has changed. We must query the AssetManager's
- // available Locales and figure out the best matching Locale in the new
- // LocaleList.
- availableLocales = mAssets.getNonSystemLocales();
+ // The LocaleList has changed. We must query the AssetManager's
+ // available Locales and figure out the best matching Locale in the new
+ // LocaleList.
+ availableLocales = mAssets.getNonSystemLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- // No app defined locales, so grab the system locales.
- availableLocales = mAssets.getLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
- }
+ availableLocales = null;
}
+ }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null) {
- selectedLocales = new String[]{
- adjustLanguageTag(bestLocale.toLanguageTag())};
- if (!bestLocale.equals(locales.get(0))) {
- mConfiguration.setLocales(
- new LocaleList(bestLocale, locales));
- }
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
}
}
- } else {
- selectedLocales = locales.getIntersection(
- ResourcesManager.getInstance().getLocaleList());
- defaultLocale = ResourcesManager.getInstance()
- .getLocaleList().get(0).toLanguageTag();
}
}
}
if (selectedLocales == null) {
- if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
- selectedLocales = new String[]{
- adjustLanguageTag(locales.get(0).toLanguageTag())};
- } else {
- selectedLocales = new String[locales.size()];
- for (int i = 0; i < locales.size(); i++) {
- selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
- }
- }
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
}
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 59def9f..d1e101d 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -257,7 +257,7 @@
public static SensorAdditionalInfo createLocalGeomagneticField(
float strength, float declination, float inclination) {
if (strength < 10 || strength > 100 // much beyond extreme values on earth
- || declination < 0 || declination > Math.PI
+ || declination < -Math.PI / 2 || declination > Math.PI / 2
|| inclination < -Math.PI / 2 || inclination > Math.PI / 2) {
throw new IllegalArgumentException("Geomagnetic field info out of range");
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 181ab2c..994037b 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1149,11 +1149,7 @@
"remove holder for requestId %d, "
+ "because lastFrame is %d.",
requestId, lastFrameNumber));
- }
- }
- if (holder != null) {
- if (DEBUG) {
Log.v(TAG, "immediately trigger onCaptureSequenceAborted because"
+ " request did not reach HAL");
}
@@ -2180,11 +2176,9 @@
final CaptureCallbackHolder holder =
CameraDeviceImpl.this.mCaptureCallbackMap.get(requestId);
- final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
boolean isPartialResult =
(resultExtras.getPartialResultCount() < mTotalPartialCount);
- int requestType = request.getRequestType();
// Check if we have a callback for this
if (holder == null) {
@@ -2194,12 +2188,11 @@
+ frameNumber);
}
- updateTracker(requestId, frameNumber, requestType, /*result*/null,
- isPartialResult);
-
return;
}
+ final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
+ int requestType = request.getRequestType();
if (isClosed()) {
if (DEBUG) {
Log.d(TAG,
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index c0a44b1..f1ae9be 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -890,10 +890,13 @@
mSystemCallingHideSoftInput = true;
mCurHideInputToken = hideInputToken;
mCurStatsToken = statsToken;
- hideSoftInput(flags, resultReceiver);
- mCurStatsToken = null;
- mCurHideInputToken = null;
- mSystemCallingHideSoftInput = false;
+ try {
+ hideSoftInput(flags, resultReceiver);
+ } finally {
+ mCurStatsToken = null;
+ mCurHideInputToken = null;
+ mSystemCallingHideSoftInput = false;
+ }
}
/**
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index ed14652..1a3dcee 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -126,50 +126,65 @@
/**
* Returns true if IP forwarding is enabled
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Use {@code android.net.INetd#ipfwdEnabled}")
boolean getIpForwardingEnabled();
/**
* Enables/Disables IP Forwarding
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}. See also "
+ + "{@code INetd#ipfwdEnableForwarding(String)}.")
void setIpForwardingEnabled(boolean enabled);
/**
* Start tethering services with the specified dhcp server range
* arg is a set of start end pairs defining the ranges.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#startTethering}")
void startTethering(in String[] dhcpRanges);
/**
* Stop currently running tethering services
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#stopTethering(int)}")
void stopTethering();
/**
* Returns true if tethering services are started
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Generally track your own tethering requests. "
+ + "See also {@code android.net.INetd#tetherIsEnabled()}")
boolean isTetheringStarted();
/**
* Tethers the specified interface
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}. See also "
+ + "{@code com.android.net.module.util.NetdUtils#tetherInterface}.")
void tetherInterface(String iface);
/**
* Untethers the specified interface
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, disable "
+ + "tethering with {@code android.net.TetheringManager#stopTethering(int)}. "
+ + "See also {@code NetdUtils#untetherInterface}.")
void untetherInterface(String iface);
/**
* Returns a list of currently tethered interfaces
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "{@code android.net.TetheringManager#getTetheredIfaces()}")
String[] listTetheredInterfaces();
/**
@@ -177,13 +192,17 @@
* The address and netmask of the external interface is used for
* the NAT'ed network.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, enable tethering with "
+ + "{@code android.net.TetheringManager#startTethering}.")
void enableNat(String internalInterface, String externalInterface);
/**
* Disables Network Address Translation between two interfaces.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = 34, trackingBug = 170729553,
+ publicAlternatives = "Avoid using this directly. Instead, disable tethering with "
+ + "{@code android.net.TetheringManager#stopTethering(int)}.")
void disableNat(String internalInterface, String externalInterface);
/**
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
new file mode 100644
index 0000000..851aa6d
--- /dev/null
+++ b/core/java/android/os/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os"
+
+flag {
+ name: "disallow_cellular_null_ciphers_restriction"
+ namespace: "cellular_security"
+ description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
+ bug: "276752881"
+}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 22e8251..8961846 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -20,8 +20,11 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
+import android.os.IInstalld;
import android.os.IVold;
+import android.os.ParcelFileDescriptor;
+import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -185,4 +188,17 @@
public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
List<UserInfo> users);
+ /**
+ * A proxy call to the corresponding method in Installer.
+ * @see com.android.server.pm.Installer#createFsveritySetupAuthToken()
+ */
+ public abstract IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+ ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId) throws IOException;
+
+ /**
+ * A proxy call to the corresponding method in Installer.
+ * @see com.android.server.pm.Installer#enableFsverity()
+ */
+ public abstract int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws IOException;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 522caac..c3c802b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5885,15 +5885,6 @@
public static final String MULTI_AUDIO_FOCUS_ENABLED = "multi_audio_focus_enabled";
/**
- * Whether desktop mode is enabled or not.
- * 0 = off
- * 1 = on
- * @hide
- */
- @Readable
- public static final String DESKTOP_MODE = "desktop_mode";
-
- /**
* The information of locale preference. This records user's preference to avoid
* unsynchronized and existing locale preference in
* {@link Locale#getDefault(Locale.Category)}.
@@ -6066,7 +6057,6 @@
PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT);
PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE);
PRIVATE_SETTINGS.add(DISPLAY_COLOR_MODE_VENDOR_HINT);
- PRIVATE_SETTINGS.add(DESKTOP_MODE);
PRIVATE_SETTINGS.add(LOCALE_PREFERENCES);
PRIVATE_SETTINGS.add(TOUCHPAD_POINTER_SPEED);
PRIVATE_SETTINGS.add(TOUCHPAD_NATURAL_SCROLLING);
diff --git a/core/java/android/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index 266046e..7869404 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -16,12 +16,21 @@
package android.security;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
+import android.os.IInstalld.IFsveritySetupAuthToken;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.system.ErrnoException;
+import com.android.internal.security.VerityUtils;
+
+import java.io.File;
+import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@@ -55,6 +64,67 @@
}
/**
+ * Enables fs-verity to the owned file under the calling app's private directory. It always uses
+ * the common configuration, i.e. SHA-256 digest algorithm, 4K block size, and without salt.
+ *
+ * The operation can only succeed when the file is not opened as writable by any process.
+ *
+ * It takes O(file size) time to build the underlying data structure for continuous
+ * verification. The operation is atomic, i.e. it's either enabled or not, even in case of
+ * power failure during or after the call.
+ *
+ * Note for the API users: When the file's authenticity is crucial, the app typical needs to
+ * perform a signature check by itself before using the file. The signature is often delivered
+ * as a separate file and stored next to the targeting file in the filesystem. The public key of
+ * the signer (normally the same app developer) can be put in the APK, and the app can use the
+ * public key to verify the signature to the file's actual fs-verity digest (from {@link
+ * #getFsverityDigest}) before using the file. The exact format is not prescribed by the
+ * framework. App developers may choose to use common practices like JCA for the signing and
+ * verification, or their own preferred approach.
+ *
+ * @param file The file to enable fs-verity. It should be an absolute path.
+ *
+ * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+ */
+ @FlaggedApi(Flags.FLAG_FSVERITY_API)
+ public void setupFsverity(@NonNull File file) throws IOException {
+ if (!file.isAbsolute()) {
+ throw new IllegalArgumentException("Expect an absolute path");
+ }
+ IFsveritySetupAuthToken authToken;
+ // fs-verity setup requires no writable fd to the file. Make sure it's closed before
+ // continue.
+ try (var authFd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)) {
+ authToken = mService.createAuthToken(authFd);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ try {
+ int errno = mService.setupFsverity(authToken, file.getPath(),
+ mContext.getPackageName());
+ if (errno != 0) {
+ new ErrnoException("setupFsverity", errno).rethrowAsIOException();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the fs-verity digest for the owned file under the calling app's
+ * private directory, or null when the file does not have fs-verity enabled.
+ *
+ * @param file The file to measure the fs-verity digest.
+ * @return The fs-verity digeset in byte[], null if none.
+ * @see <a href="https://www.kernel.org/doc/html/next/filesystems/fsverity.html">Kernel doc</a>
+ */
+ @FlaggedApi(Flags.FLAG_FSVERITY_API)
+ public @Nullable byte[] getFsverityDigest(@NonNull File file) throws IOException {
+ return VerityUtils.getFsverityDigest(file.getPath());
+ }
+
+ /**
* Returns whether the given certificate can be used to prove app's install source. Always
* return false if the feature is not supported.
*
diff --git a/core/java/android/security/IFileIntegrityService.aidl b/core/java/android/security/IFileIntegrityService.aidl
index dff347e..1a6cf88 100644
--- a/core/java/android/security/IFileIntegrityService.aidl
+++ b/core/java/android/security/IFileIntegrityService.aidl
@@ -16,6 +16,9 @@
package android.security;
+import android.os.ParcelFileDescriptor;
+import android.os.IInstalld;
+
/**
* Binder interface to communicate with FileIntegrityService.
* @hide
@@ -23,4 +26,8 @@
interface IFileIntegrityService {
boolean isApkVeritySupported();
boolean isAppSourceCertificateTrusted(in byte[] certificateBytes, in String packageName);
+
+ IInstalld.IFsveritySetupAuthToken createAuthToken(in ParcelFileDescriptor authFd);
+ int setupFsverity(IInstalld.IFsveritySetupAuthToken authToken, in String filePath,
+ in String packageName);
}
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 22b1f02..96c0be7 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -6,4 +6,5 @@
per-file *NetworkSecurityPolicy.java = file:net/OWNERS
per-file Confirmation*.java = file:/keystore/OWNERS
-per-file FileIntegrityManager.java = victorhsieh@google.com
+per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS
+per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 55133ae..9b1132a 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -46,7 +46,14 @@
* <p> See details on usage of {@code Action} for various actionable entries in
* {@link BeginCreateCredentialResponse} and {@link BeginGetCredentialResponse}.
*
- * @param slice the display content to be displayed on the UI, along with this action
+ * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+ * through the {@link androidx.credentials.provider} Jetpack library;
+ * If constructed manually, the {@code slice} object must
+ * contain the non-null properties of the
+ * {@link androidx.credentials.provider.Action} class populated as slice items
+ * against specific hints as used in the class's {@code toSlice} method,
+ * since the Android System uses this library to parse the {@code slice} and
+ * extract the required attributes
*/
public Action(@NonNull Slice slice) {
Objects.requireNonNull(slice, "slice must not be null");
diff --git a/core/java/android/service/credentials/CreateEntry.java b/core/java/android/service/credentials/CreateEntry.java
index 6a9f09f..2495c7d 100644
--- a/core/java/android/service/credentials/CreateEntry.java
+++ b/core/java/android/service/credentials/CreateEntry.java
@@ -66,7 +66,14 @@
/**
* Constructs a CreateEntry to be displayed on the UI.
*
- * @param slice the display content to be displayed on the UI, along with this entry
+ * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+ * through the {@link androidx.credentials.provider} Jetpack library;
+ * If constructed manually, the {@code slice} object must
+ * contain the non-null properties of the
+ * {@link androidx.credentials.provider.CreateEntry} class populated as slice items
+ * against specific hints as used in the class's {@code toSlice} method,
+ * since the Android System uses this library to parse the {@code slice} and
+ * extract the required attributes
*/
public CreateEntry(
@NonNull Slice slice) {
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 512d833..53094e8 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -69,8 +69,14 @@
* receive the complete corresponding
* {@link GetCredentialRequest}.
* @param type the type of the credential for which this credential entry is being created
- * @param slice the slice containing the metadata to be shown on the UI. Must be
- * constructed through the androidx.credentials jetpack library.
+ * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+ * through the {@link androidx.credentials.provider} Jetpack library;
+ * If constructed manually, the {@code slice} object must
+ * contain the non-null properties of the
+ * {@link androidx.credentials.provider.CredentialEntry} class populated as slice
+ * items against specific hints as used in the class's {@code toSlice} method,
+ * since the Android System uses this library to parse the {@code slice} and
+ * extract the required attributes
*
* @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type}
* is null, or empty
diff --git a/core/java/android/service/credentials/RemoteEntry.java b/core/java/android/service/credentials/RemoteEntry.java
index 5b3218a..5fd9925 100644
--- a/core/java/android/service/credentials/RemoteEntry.java
+++ b/core/java/android/service/credentials/RemoteEntry.java
@@ -73,7 +73,14 @@
/**
* Constructs a RemoteEntry to be displayed on the UI.
*
- * @param slice the display content to be displayed on the UI, along with this entry
+ * @param slice the slice containing the metadata to be shown on the UI, must be constructed
+ * through the {@link androidx.credentials.provider} Jetpack library;
+ * If constructed manually, the {@code slice} object must
+ * contain the non-null properties of the
+ * {@link androidx.credentials.provider.RemoteEntry} class populated as slice items
+ * against specific hints as used in the class's {@code toSlice} method,
+ * since the Android System uses this library to parse the {@code slice} and
+ * extract the required attributes
*/
public RemoteEntry(
@NonNull Slice slice) {
diff --git a/core/java/android/service/dreams/OWNERS b/core/java/android/service/dreams/OWNERS
index 489a5f6..77bcee8 100644
--- a/core/java/android/service/dreams/OWNERS
+++ b/core/java/android/service/dreams/OWNERS
@@ -4,5 +4,7 @@
dsandler@google.com
galinap@google.com
jjaggi@google.com
+lusilva@google.com
michaelwr@google.com
santoscordon@google.com
+wxyz@google.com
diff --git a/core/java/android/service/trust/OWNERS b/core/java/android/service/trust/OWNERS
index a895f7f..16eb19a 100644
--- a/core/java/android/service/trust/OWNERS
+++ b/core/java/android/service/trust/OWNERS
@@ -1,4 +1,3 @@
# Bug component: 36824
-cbrubaker@google.com
jacobhobbie@google.com
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.aidl b/core/java/android/service/voice/HotwordTrainingAudio.aidl
new file mode 100644
index 0000000..4dd2289
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingAudio.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+parcelable HotwordTrainingAudio;
\ No newline at end of file
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
new file mode 100644
index 0000000..895b0c0
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.media.AudioFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Represents audio supporting hotword model training.
+ *
+ * @hide
+ */
+@DataClass(
+ genConstructor = false,
+ genBuilder = true,
+ genEqualsHashCode = true,
+ genHiddenConstDefs = true,
+ genParcelable = true,
+ genToString = true
+)
+@SystemApi
+public final class HotwordTrainingAudio implements Parcelable {
+ /** Represents unset value for the hotword offset. */
+ public static final int HOTWORD_OFFSET_UNSET = -1;
+
+ /** Buffer of hotword audio data for training models. */
+ @NonNull
+ private final byte[] mHotwordAudio;
+
+ private String hotwordAudioToString() {
+ return "length=" + mHotwordAudio.length;
+ }
+
+ /**
+ * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
+ */
+ @NonNull
+ private final AudioFormat mAudioFormat;
+
+ /**
+ * App-defined identifier to distinguish hotword training audio instances.
+ */
+ @NonNull
+ private final int mAudioType;
+
+ private static int defaultAudioType() {
+ return 0;
+ }
+
+ /**
+ * App-defined offset in milliseconds relative to start of
+ * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is
+ * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}.
+ */
+ private int mHotwordOffsetMillis = HOTWORD_OFFSET_UNSET;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ HotwordTrainingAudio(
+ @NonNull byte[] hotwordAudio,
+ @NonNull AudioFormat audioFormat,
+ @NonNull int audioType,
+ int hotwordOffsetMillis) {
+ this.mHotwordAudio = hotwordAudio;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHotwordAudio);
+ this.mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ this.mAudioType = audioType;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioType);
+ this.mHotwordOffsetMillis = hotwordOffsetMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Buffer of hotword audio data for training models.
+ */
+ @DataClass.Generated.Member
+ public @NonNull byte[] getHotwordAudio() {
+ return mHotwordAudio;
+ }
+
+ /**
+ * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * App-defined identifier to distinguish hotword training audio instances.
+ */
+ @DataClass.Generated.Member
+ public @NonNull int getAudioType() {
+ return mAudioType;
+ }
+
+ /**
+ * App-defined offset in milliseconds relative to start of
+ * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is
+ * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}.
+ */
+ @DataClass.Generated.Member
+ public int getHotwordOffsetMillis() {
+ return mHotwordOffsetMillis;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "HotwordTrainingAudio { " +
+ "hotwordAudio = " + hotwordAudioToString() + ", " +
+ "audioFormat = " + mAudioFormat + ", " +
+ "audioType = " + mAudioType + ", " +
+ "hotwordOffsetMillis = " + mHotwordOffsetMillis +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HotwordTrainingAudio other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HotwordTrainingAudio that = (HotwordTrainingAudio) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Arrays.equals(mHotwordAudio, that.mHotwordAudio)
+ && java.util.Objects.equals(mAudioFormat, that.mAudioFormat)
+ && mAudioType == that.mAudioType
+ && mHotwordOffsetMillis == that.mHotwordOffsetMillis;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Arrays.hashCode(mHotwordAudio);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mAudioFormat);
+ _hash = 31 * _hash + mAudioType;
+ _hash = 31 * _hash + mHotwordOffsetMillis;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeByteArray(mHotwordAudio);
+ dest.writeTypedObject(mAudioFormat, flags);
+ dest.writeInt(mAudioType);
+ dest.writeInt(mHotwordOffsetMillis);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HotwordTrainingAudio(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte[] hotwordAudio = in.createByteArray();
+ AudioFormat audioFormat = (AudioFormat) in.readTypedObject(AudioFormat.CREATOR);
+ int audioType = in.readInt();
+ int hotwordOffsetMillis = in.readInt();
+
+ this.mHotwordAudio = hotwordAudio;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHotwordAudio);
+ this.mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ this.mAudioType = audioType;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioType);
+ this.mHotwordOffsetMillis = hotwordOffsetMillis;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<HotwordTrainingAudio> CREATOR
+ = new Parcelable.Creator<HotwordTrainingAudio>() {
+ @Override
+ public HotwordTrainingAudio[] newArray(int size) {
+ return new HotwordTrainingAudio[size];
+ }
+
+ @Override
+ public HotwordTrainingAudio createFromParcel(@NonNull Parcel in) {
+ return new HotwordTrainingAudio(in);
+ }
+ };
+
+ /**
+ * A builder for {@link HotwordTrainingAudio}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull byte[] mHotwordAudio;
+ private @NonNull AudioFormat mAudioFormat;
+ private @NonNull int mAudioType;
+ private int mHotwordOffsetMillis;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param hotwordAudio
+ * Buffer of hotword audio data for training models.
+ * @param audioFormat
+ * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
+ */
+ public Builder(
+ @NonNull byte[] hotwordAudio,
+ @NonNull AudioFormat audioFormat) {
+ mHotwordAudio = hotwordAudio;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHotwordAudio);
+ mAudioFormat = audioFormat;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mAudioFormat);
+ }
+
+ /**
+ * Buffer of hotword audio data for training models.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setHotwordAudio(@NonNull byte... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mHotwordAudio = value;
+ return this;
+ }
+
+ /**
+ * The {@link AudioFormat} of the {@link HotwordTrainingAudio#mHotwordAudio}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAudioFormat(@NonNull AudioFormat value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAudioFormat = value;
+ return this;
+ }
+
+ /**
+ * App-defined identifier to distinguish hotword training audio instances.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setAudioType(@NonNull int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mAudioType = value;
+ return this;
+ }
+
+ /**
+ * App-defined offset in milliseconds relative to start of
+ * {@link HotwordTrainingAudio#mHotwordAudio}. Default value is
+ * {@link HotwordTrainingAudio#HOTWORD_OFFSET_UNSET}.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setHotwordOffsetMillis(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mHotwordOffsetMillis = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull HotwordTrainingAudio build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mAudioType = defaultAudioType();
+ }
+ if ((mBuilderFieldsSet & 0x8) == 0) {
+ mHotwordOffsetMillis = HOTWORD_OFFSET_UNSET;
+ }
+ HotwordTrainingAudio o = new HotwordTrainingAudio(
+ mHotwordAudio,
+ mAudioFormat,
+ mAudioType,
+ mHotwordOffsetMillis);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1692837160437L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
+ inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/voice/HotwordTrainingData.aidl b/core/java/android/service/voice/HotwordTrainingData.aidl
new file mode 100644
index 0000000..03cc841
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+parcelable HotwordTrainingData;
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
new file mode 100644
index 0000000..9dca77e
--- /dev/null
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.voice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Contains training data related to hotword detection service.
+ *
+ * <p>The constructed object's size must be within
+ * {@link HotwordTrainingData#getMaxTrainingDataSize()} or an
+ * {@link IllegalArgumentException} will be thrown on construction. Size of the object is calculated
+ * by converting object to a {@link Parcel} and using the {@link Parcel#dataSize()}.
+ *
+ * @hide
+ */
+@DataClass(
+ genConstructor = false,
+ genBuilder = true,
+ genEqualsHashCode = true,
+ genHiddenConstDefs = true,
+ genParcelable = true,
+ genToString = true)
+@SystemApi
+public final class HotwordTrainingData implements Parcelable {
+ /** Max size for hotword training data. */
+ public static int getMaxTrainingDataSize() {
+ return 1024 * 1024; // 1 MB;
+ }
+
+ /** The list containing hotword audio that is useful for training. */
+ @NonNull
+ @DataClass.PluralOf("trainingAudio")
+ private final List<HotwordTrainingAudio> mTrainingAudios;
+
+ private static List<HotwordTrainingAudio> defaultTrainingAudios() {
+ return Collections.emptyList();
+ }
+
+ /** Timeout stage is unknown. */
+ public static final int TIMEOUT_STAGE_UNKNOWN = 0;
+
+ /**
+ * Timeout stage value that represents that the model timed out very early while detecting
+ * hotword.
+ */
+ public static final int TIMEOUT_STAGE_VERY_EARLY = 1;
+
+ /**
+ * Timeout stage value that represents that the model timed out early while detecting
+ * hotword.
+ */
+ public static final int TIMEOUT_STAGE_EARLY = 2;
+
+ /**
+ * Timeout stage value that represents that the model timed out in the middle while detecting
+ * hotword.
+ */
+ public static final int TIMEOUT_STAGE_MIDDLE = 3;
+
+ /**
+ * Timeout stage value that represents that the model timed out late while detecting
+ * hotword.
+ */
+ public static final int TIMEOUT_STAGE_LATE = 4;
+
+ /** @hide */
+ @IntDef(prefix = {"TIMEOUT_STAGE"}, value = {
+ TIMEOUT_STAGE_UNKNOWN,
+ TIMEOUT_STAGE_VERY_EARLY,
+ TIMEOUT_STAGE_EARLY,
+ TIMEOUT_STAGE_MIDDLE,
+ TIMEOUT_STAGE_LATE,
+ })
+ @interface HotwordTimeoutStage {}
+
+ /** Stage when timeout occurred. */
+ @HotwordTimeoutStage
+ private final int mTimeoutStage;
+
+ private static int defaultTimeoutStage() {
+ return TIMEOUT_STAGE_UNKNOWN;
+ }
+
+ private void onConstructed() {
+ // Verify size of object is within limit.
+ Parcel parcel = Parcel.obtain();
+ parcel.writeValue(this);
+ int dataSizeBytes = parcel.dataSize();
+ parcel.recycle();
+ Preconditions.checkArgument(
+ dataSizeBytes < getMaxTrainingDataSize(),
+ TextUtils.formatSimple(
+ "Hotword training data of size %s exceeds size limit of %s!",
+ dataSizeBytes, getMaxTrainingDataSize()));
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/voice/HotwordTrainingData.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "TIMEOUT_STAGE_", value = {
+ TIMEOUT_STAGE_UNKNOWN,
+ TIMEOUT_STAGE_VERY_EARLY,
+ TIMEOUT_STAGE_EARLY,
+ TIMEOUT_STAGE_MIDDLE,
+ TIMEOUT_STAGE_LATE
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface TimeoutStage {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String timeoutStageToString(@TimeoutStage int value) {
+ switch (value) {
+ case TIMEOUT_STAGE_UNKNOWN:
+ return "TIMEOUT_STAGE_UNKNOWN";
+ case TIMEOUT_STAGE_VERY_EARLY:
+ return "TIMEOUT_STAGE_VERY_EARLY";
+ case TIMEOUT_STAGE_EARLY:
+ return "TIMEOUT_STAGE_EARLY";
+ case TIMEOUT_STAGE_MIDDLE:
+ return "TIMEOUT_STAGE_MIDDLE";
+ case TIMEOUT_STAGE_LATE:
+ return "TIMEOUT_STAGE_LATE";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ HotwordTrainingData(
+ @NonNull List<HotwordTrainingAudio> trainingAudios,
+ @HotwordTimeoutStage int timeoutStage) {
+ this.mTrainingAudios = trainingAudios;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTrainingAudios);
+ this.mTimeoutStage = timeoutStage;
+ com.android.internal.util.AnnotationValidations.validate(
+ HotwordTimeoutStage.class, null, mTimeoutStage);
+
+ onConstructed();
+ }
+
+ /**
+ * The list containing hotword audio that is useful for training.
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<HotwordTrainingAudio> getTrainingAudios() {
+ return mTrainingAudios;
+ }
+
+ /**
+ * Stage when timeout occurred.
+ */
+ @DataClass.Generated.Member
+ public @HotwordTimeoutStage int getTimeoutStage() {
+ return mTimeoutStage;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "HotwordTrainingData { " +
+ "trainingAudios = " + mTrainingAudios + ", " +
+ "timeoutStage = " + mTimeoutStage +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HotwordTrainingData other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HotwordTrainingData that = (HotwordTrainingData) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mTrainingAudios, that.mTrainingAudios)
+ && mTimeoutStage == that.mTimeoutStage;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudios);
+ _hash = 31 * _hash + mTimeoutStage;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeParcelableList(mTrainingAudios, flags);
+ dest.writeInt(mTimeoutStage);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HotwordTrainingData(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ List<HotwordTrainingAudio> trainingAudios = new ArrayList<>();
+ in.readParcelableList(trainingAudios, HotwordTrainingAudio.class.getClassLoader());
+ int timeoutStage = in.readInt();
+
+ this.mTrainingAudios = trainingAudios;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mTrainingAudios);
+ this.mTimeoutStage = timeoutStage;
+ com.android.internal.util.AnnotationValidations.validate(
+ HotwordTimeoutStage.class, null, mTimeoutStage);
+
+ onConstructed();
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<HotwordTrainingData> CREATOR
+ = new Parcelable.Creator<HotwordTrainingData>() {
+ @Override
+ public HotwordTrainingData[] newArray(int size) {
+ return new HotwordTrainingData[size];
+ }
+
+ @Override
+ public HotwordTrainingData createFromParcel(@NonNull Parcel in) {
+ return new HotwordTrainingData(in);
+ }
+ };
+
+ /**
+ * A builder for {@link HotwordTrainingData}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @NonNull List<HotwordTrainingAudio> mTrainingAudios;
+ private @HotwordTimeoutStage int mTimeoutStage;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The list containing hotword audio that is useful for training.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTrainingAudios(@NonNull List<HotwordTrainingAudio> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mTrainingAudios = value;
+ return this;
+ }
+
+ /** @see #setTrainingAudios */
+ @DataClass.Generated.Member
+ public @NonNull Builder addTrainingAudio(@NonNull HotwordTrainingAudio value) {
+ if (mTrainingAudios == null) setTrainingAudios(new ArrayList<>());
+ mTrainingAudios.add(value);
+ return this;
+ }
+
+ /**
+ * Stage when timeout occurred.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setTimeoutStage(@HotwordTimeoutStage int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mTimeoutStage = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull HotwordTrainingData build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mTrainingAudios = defaultTrainingAudios();
+ }
+ if ((mBuilderFieldsSet & 0x2) == 0) {
+ mTimeoutStage = defaultTimeoutStage();
+ }
+ HotwordTrainingData o = new HotwordTrainingData(
+ mTrainingAudios,
+ mTimeoutStage);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x4) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1693313864628L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudios\npublic static final int TIMEOUT_STAGE_UNKNOWN\npublic static final int TIMEOUT_STAGE_VERY_EARLY\npublic static final int TIMEOUT_STAGE_EARLY\npublic static final int TIMEOUT_STAGE_MIDDLE\npublic static final int TIMEOUT_STAGE_LATE\nprivate final @android.service.voice.HotwordTrainingData.HotwordTimeoutStage int mTimeoutStage\npublic static int getMaxTrainingDataSize()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudios()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 23afb03..a208d1f 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -81,6 +81,8 @@
private int mConnectionCount = 0;
private final InputMethodManager mImm;
+ private final Rect mTempRect = new Rect();
+
private final RectF mTempRectF = new RectF();
private final Region mTempRegion = new Region();
@@ -401,8 +403,9 @@
final View cachedHoverTarget = getCachedHoverTarget();
if (cachedHoverTarget != null) {
- final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
- if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
+ final Rect handwritingArea = mTempRect;
+ if (getViewHandwritingArea(cachedHoverTarget, handwritingArea)
+ && isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
/* isHover */ true)
&& shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
return cachedHoverTarget;
@@ -445,8 +448,9 @@
// directly return the connectedView.
final View connectedView = getConnectedView();
if (connectedView != null) {
- Rect handwritingArea = getViewHandwritingArea(connectedView);
- if (isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
+ Rect handwritingArea = mTempRect;
+ if (getViewHandwritingArea(connectedView, handwritingArea)
+ && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
&& shouldTriggerStylusHandwritingForView(connectedView)) {
return connectedView;
}
@@ -528,28 +532,30 @@
/**
* Return the handwriting area of the given view, represented in the window's coordinate.
* If the view didn't set any handwriting area, it will return the view's boundary.
- * It will return null if the view or its handwriting area is not visible.
*
- * The handwriting area is clipped to its visible part.
+ * <p> The handwriting area is clipped to its visible part.
* Notice that the returned rectangle is the view's original handwriting area without the
- * view's handwriting area extends.
+ * view's handwriting area extends. </p>
+ *
+ * @param view the {@link View} whose handwriting area we want to compute.
+ * @param rect the {@link Rect} to receive the result.
+ *
+ * @return true if the view's handwriting area is still visible, or false if it's clipped and
+ * fully invisible. This method only consider the clip by given view's parents, but not the case
+ * where a view is covered by its sibling view.
*/
- @Nullable
- private static Rect getViewHandwritingArea(@NonNull View view) {
+ private static boolean getViewHandwritingArea(@NonNull View view, @NonNull Rect rect) {
final ViewParent viewParent = view.getParent();
if (viewParent != null && view.isAttachedToWindow() && view.isAggregatedVisible()) {
final Rect localHandwritingArea = view.getHandwritingArea();
- final Rect globalHandwritingArea = new Rect();
if (localHandwritingArea != null) {
- globalHandwritingArea.set(localHandwritingArea);
+ rect.set(localHandwritingArea);
} else {
- globalHandwritingArea.set(0, 0, view.getWidth(), view.getHeight());
+ rect.set(0, 0, view.getWidth(), view.getHeight());
}
- if (viewParent.getChildVisibleRect(view, globalHandwritingArea, null)) {
- return globalHandwritingArea;
- }
+ return viewParent.getChildVisibleRect(view, rect, null);
}
- return null;
+ return false;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 62e37a4..b8385c6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1318,8 +1318,8 @@
* that have the ignore orientation request display setting enabled by OEMs
* (enables compatibility mode for fixed orientation on Android 12 (API
* level 31) or higher; see
- * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
- * Large screen app compatibility</a>
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
+ * Large screen compatibility mode</a>
* for more details).
*
* <p>To opt out of the user aspect ratio compatibility override, add this property
@@ -1358,8 +1358,8 @@
* that have the ignore orientation request display setting enabled by OEMs
* (enables compatibility mode for fixed orientation on Android 12 (API
* level 31) or higher; see
- * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
- * Large screen app compatibility</a>
+ * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-compatibility-mode">
+ * Large screen compatibility mode</a>
* for more details).
*
* <p>To opt out of the full-screen option of the user aspect ratio compatibility
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index fff778c..755113b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -33,6 +33,7 @@
import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED;
import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.STATE_FGS;
+import static com.android.internal.app.procstats.ProcessStats.STATE_FROZEN;
import static com.android.internal.app.procstats.ProcessStats.STATE_HEAVY_WEIGHT;
import static com.android.internal.app.procstats.ProcessStats.STATE_HOME;
import static com.android.internal.app.procstats.ProcessStats.STATE_IMPORTANT_BACKGROUND;
@@ -142,6 +143,7 @@
private ProcessState mCommonProcess;
private int mCurCombinedState = STATE_NOTHING;
private long mStartTime;
+ private int mStateBeforeFrozen = STATE_NOTHING;
private int mLastPssState = STATE_NOTHING;
private long mLastPssTime;
@@ -423,6 +425,27 @@
}
/**
+ * Used to notify that this process was frozen.
+ */
+ public void onProcessFrozen(long now,
+ ArrayMap<String, ProcessStateHolder> pkgList) {
+ mStateBeforeFrozen = mCurCombinedState % STATE_COUNT;
+ int currentMemFactor = mCurCombinedState / STATE_COUNT;
+ int combinedState = STATE_FROZEN + (currentMemFactor * STATE_COUNT);
+ setCombinedState(combinedState, now, pkgList);
+ }
+
+ /**
+ * Used to notify that this process was unfrozen.
+ */
+ public void onProcessUnfrozen(long now,
+ ArrayMap<String, ProcessStateHolder> pkgList) {
+ int currentMemFactor = mCurCombinedState / STATE_COUNT;
+ int combinedState = mStateBeforeFrozen + (currentMemFactor * STATE_COUNT);
+ setCombinedState(combinedState, now, pkgList);
+ }
+
+ /**
* Update the current state of the given list of processes.
*
* @param state Current ActivityManager.PROCESS_STATE_*
@@ -434,13 +457,20 @@
ArrayMap<String, ProcessStateHolder> pkgList) {
if (state < 0) {
state = mNumStartedServices > 0
- ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
+ ? (STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT)) : STATE_NOTHING;
} else {
- state = PROCESS_STATE_TO_STATE[state] + (memFactor*STATE_COUNT);
+ state = PROCESS_STATE_TO_STATE[state] + (memFactor * STATE_COUNT);
}
+ setCombinedState(state, now, pkgList);
+ }
+ /**
+ * Sets combined state on the corresponding ProcessState objects.
+ */
+ void setCombinedState(int state, long now,
+ ArrayMap<String, ProcessStateHolder> pkgList) {
// First update the common process.
- mCommonProcess.setCombinedState(state, now);
+ mCommonProcess.setCombinedStateIdv(state, now);
// If the common process is not multi-package, there is nothing else to do.
if (!mCommonProcess.mMultiPackage) {
@@ -449,12 +479,15 @@
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).setCombinedState(state, now);
+ pullFixedProc(pkgList, ip).setCombinedStateIdv(state, now);
}
}
}
- public void setCombinedState(int state, long now) {
+ /**
+ * Sets the combined state for this individual ProcessState object.
+ */
+ void setCombinedStateIdv(int state, long now) {
ensureNotDead();
if (!mDead && (mCurCombinedState != state)) {
//Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
@@ -545,7 +578,7 @@
}
mNumStartedServices++;
if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
- setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+ setCombinedStateIdv(STATE_SERVICE_RESTARTING + (memFactor * STATE_COUNT), now);
}
}
@@ -561,7 +594,7 @@
}
mNumStartedServices--;
if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
- setCombinedState(STATE_NOTHING, now);
+ setCombinedStateIdv(STATE_NOTHING, now);
} else if (mNumStartedServices < 0) {
Slog.wtfStack(TAG, "Proc started services underrun: pkg="
+ mPackage + " uid=" + mUid + " name=" + mName);
@@ -1588,7 +1621,9 @@
case STATE_CACHED:
cachedMs += duration;
break;
- // TODO (b/261910877) Add support for tracking frozenMs.
+ case STATE_FROZEN:
+ frozenMs += duration;
+ break;
}
}
statsEventOutput.write(
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 0a69ea8..048912c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -118,9 +118,6 @@
*/
public static final String NAS_DEFAULT_SERVICE = "nas_default_service";
- /** (boolean) Whether notify() calls to NMS should acquire and hold WakeLocks. */
- public static final String NOTIFY_WAKELOCK = "nms_notify_wakelock";
-
// Flags related to media notifications
/**
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 9233050..8de448b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -74,10 +74,6 @@
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
- /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
- public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
- releasedFlag("persist.sysui.notification.wake_lock_for_posting_notification");
-
/** Gating storing NotificationRankingUpdate ranking map in shared memory. */
public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
"persist.sysui.notification.ranking_update_ashmem");
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 116c301c..3e9458d 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -342,7 +342,11 @@
synchronized (mLock) {
int samplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
DEFAULT_SAMPLING_INTERVAL);
+ boolean wasEnabled = mEnabled;
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
+ if (wasEnabled != mEnabled) {
+ Log.d(TAG, "Latency tracker " + (mEnabled ? "enabled" : "disabled") + ".");
+ }
for (int action : ACTIONS_ALL) {
String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT);
int legacyActionTraceThreshold = properties.getInt(
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 21369f9..d6c5593 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -352,7 +352,7 @@
const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL);
code->internalDataPathObj = dirStr;
- code->internalDataPath = code->internalDataPathObj.string();
+ code->internalDataPath = code->internalDataPathObj.c_str();
env->ReleaseStringUTFChars(internalDataDir, dirStr);
if (externalDataDir != NULL) {
@@ -360,7 +360,7 @@
code->externalDataPathObj = dirStr;
env->ReleaseStringUTFChars(externalDataDir, dirStr);
}
- code->externalDataPath = code->externalDataPathObj.string();
+ code->externalDataPath = code->externalDataPathObj.c_str();
code->sdkVersion = sdkVersion;
@@ -372,7 +372,7 @@
code->obbPathObj = dirStr;
env->ReleaseStringUTFChars(obbDir, dirStr);
}
- code->obbPath = code->obbPathObj.string();
+ code->obbPath = code->obbPathObj.c_str();
jbyte* rawSavedState = NULL;
jsize rawSavedSize = 0;
diff --git a/core/jni/android_app_backup_FullBackup.cpp b/core/jni/android_app_backup_FullBackup.cpp
index 339a7d3..5e096d7 100644
--- a/core/jni/android_app_backup_FullBackup.cpp
+++ b/core/jni/android_app_backup_FullBackup.cpp
@@ -106,15 +106,14 @@
: NULL;
if (path.length() < rootpath.length()) {
- ALOGE("file path [%s] shorter than root path [%s]",
- path.string(), rootpath.string());
+ ALOGE("file path [%s] shorter than root path [%s]", path.c_str(), rootpath.c_str());
return (jint) -1;
}
off64_t tarSize = 0;
jint err = write_tarfile(packageName, domain, rootpath, path, &tarSize, writer);
if (!err) {
- ALOGI("measured [%s] at %lld", path.string(), (long long)tarSize);
+ ALOGI("measured [%s] at %lld", path.c_str(), (long long)tarSize);
env->CallVoidMethod(dataOutputObj, sFullBackupDataOutput.addSize, (jlong) tarSize);
}
diff --git a/core/jni/android_backup_BackupDataInput.cpp b/core/jni/android_backup_BackupDataInput.cpp
index 79fa2a2..fc081a7 100644
--- a/core/jni/android_backup_BackupDataInput.cpp
+++ b/core/jni/android_backup_BackupDataInput.cpp
@@ -76,7 +76,7 @@
return err < 0 ? err : -1;
}
// TODO: Set the fields in the entity object
- jstring keyStr = env->NewStringUTF(key.string());
+ jstring keyStr = env->NewStringUTF(key.c_str());
env->SetObjectField(entity, s_keyField, keyStr);
env->SetIntField(entity, s_dataSizeField, dataSize);
return 0;
diff --git a/core/jni/android_backup_BackupHelperDispatcher.cpp b/core/jni/android_backup_BackupHelperDispatcher.cpp
index efe7d0b..efce8e1 100644
--- a/core/jni/android_backup_BackupHelperDispatcher.cpp
+++ b/core/jni/android_backup_BackupHelperDispatcher.cpp
@@ -118,7 +118,7 @@
}
env->SetIntField(headerObj, s_chunkSizeField, flattenedHeader.dataSize);
- env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.string()));
+ env->SetObjectField(headerObj, s_keyPrefixField, env->NewStringUTF(keyPrefix.c_str()));
return (jint) 0;
}
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 2435406..60da2c2 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -58,13 +58,13 @@
msg.appendFormat("Couldn't read row %d, col %d from CursorWindow. "
"Make sure the Cursor is initialized correctly before accessing data from it.",
row, column);
- jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+ jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
static void throwUnknownTypeException(JNIEnv * env, jint type) {
String8 msg;
msg.appendFormat("UNKNOWN type %d", type);
- jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+ jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
static int getFdCount() {
@@ -107,7 +107,7 @@
fail:
jniThrowExceptionFmt(env, "android/database/CursorWindowAllocationException",
"Could not allocate CursorWindow '%s' of size %d due to error %d.",
- name.string(), cursorWindowSize, status);
+ name.c_str(), cursorWindowSize, status);
return 0;
}
@@ -139,7 +139,7 @@
static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
- return env->NewStringUTF(window->name().string());
+ return env->NewStringUTF(window->name().c_str());
}
static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jlong windowPtr,
@@ -151,7 +151,7 @@
if (status) {
String8 msg;
msg.appendFormat("Could not write CursorWindow to Parcel due to error %d.", status);
- jniThrowRuntimeException(env, msg.string());
+ jniThrowRuntimeException(env, msg.c_str());
}
}
@@ -267,7 +267,7 @@
// doesn't like UTF-8 strings with high codepoints. It actually expects
// Modified UTF-8 with encoded surrogate pairs.
String16 utf16(value, sizeIncludingNull - 1);
- return env->NewString(reinterpret_cast<const jchar*>(utf16.string()), utf16.size());
+ return env->NewString(reinterpret_cast<const jchar*>(utf16.c_str()), utf16.size());
} else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
int64_t value = window->getFieldSlotValueLong(fieldSlot);
char buf[32];
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
index daa2087..c6a7511 100644
--- a/core/jni/android_database_SQLiteCommon.cpp
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -229,7 +229,7 @@
fullMessage.append(": ");
fullMessage.append(message);
}
- jniThrowException(env, exceptionClass, fullMessage.string());
+ jniThrowException(env, exceptionClass, fullMessage.c_str());
} else {
jniThrowException(env, exceptionClass, message);
}
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index 29520c2..893cc98 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -91,15 +91,14 @@
// Called each time a statement begins execution, when tracing is enabled.
static void sqliteTraceCallback(void *data, const char *sql) {
SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
- ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n",
- connection->label.string(), sql);
+ ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n", connection->label.c_str(), sql);
}
// Called each time a statement finishes execution, when profiling is enabled.
static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) {
SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
- ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
- connection->label.string(), sql, tm * 0.000001f);
+ ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n", connection->label.c_str(),
+ sql, tm * 0.000001f);
}
// Called after each SQLite VM instruction when cancelation is enabled.
@@ -130,7 +129,7 @@
env->ReleaseStringUTFChars(labelStr, labelChars);
sqlite3* db;
- int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
+ int err = sqlite3_open_v2(path.c_str(), &db, sqliteFlags, NULL);
if (err != SQLITE_OK) {
throw_sqlite3_exception_errcode(env, err, "Could not open database");
return 0;
@@ -180,7 +179,7 @@
sqlite3_profile(db, &sqliteProfileCallback, connection);
}
- ALOGV("Opened connection %p with label '%s'", db, label.string());
+ ALOGV("Opened connection %p with label '%s'", db, label.c_str());
return reinterpret_cast<jlong>(connection);
}
@@ -760,7 +759,7 @@
if (status) {
String8 msg;
msg.appendFormat("Failed to clear the cursor window, status=%d", status);
- throw_sqlite3_exception(env, connection->db, msg.string());
+ throw_sqlite3_exception(env, connection->db, msg.c_str());
return 0;
}
@@ -770,7 +769,7 @@
String8 msg;
msg.appendFormat("Failed to set the cursor window column count to %d, status=%d",
numColumns, status);
- throw_sqlite3_exception(env, connection->db, msg.string());
+ throw_sqlite3_exception(env, connection->db, msg.c_str());
return 0;
}
@@ -845,7 +844,7 @@
String8 msg;
msg.appendFormat("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d",
requiredPos, totalRows);
- throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.string());
+ throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.c_str());
return 0;
}
diff --git a/core/jni/android_ddm_DdmHandleNativeHeap.cpp b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
index 2ca4500..d36d29c 100644
--- a/core/jni/android_ddm_DdmHandleNativeHeap.cpp
+++ b/core/jni/android_ddm_DdmHandleNativeHeap.cpp
@@ -100,8 +100,8 @@
if (array != NULL) {
env->SetByteArrayRegion(array, 0,
sizeof(header), reinterpret_cast<jbyte*>(&header));
- env->SetByteArrayRegion(array, sizeof(header),
- maps.size(), reinterpret_cast<const jbyte*>(maps.string()));
+ env->SetByteArrayRegion(array, sizeof(header), maps.size(),
+ reinterpret_cast<const jbyte*>(maps.c_str()));
env->SetByteArrayRegion(array, sizeof(header) + maps.size(),
header.allocSize, reinterpret_cast<jbyte*>(leak_info.buffer));
}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index eb49f41..5f3a1b5 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -878,7 +878,7 @@
jniThrowRuntimeException(env, "getParameters failed (empty parameters)");
return 0;
}
- return env->NewStringUTF(params8.string());
+ return env->NewStringUTF(params8.c_str());
}
static void android_hardware_Camera_reconnect(JNIEnv *env, jobject thiz)
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 3e4c7c7..041fed7 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -514,7 +514,7 @@
ssize_t res;
while ((res = TEMP_FAILURE_RETRY(read(readFd, &out[0], /*count*/1))) > 0) {
if (out[0] == '\n') {
- ALOGD("%s", logLine.string());
+ ALOGD("%s", logLine.c_str());
logLine.clear();
} else {
logLine.append(out);
@@ -527,7 +527,7 @@
errno, strerror(errno));
//return;
} else if (!logLine.empty()) {
- ALOGD("%s", logLine.string());
+ ALOGD("%s", logLine.c_str());
}
close(readFd);
@@ -956,8 +956,8 @@
return OK;
} else if (!res.isOk()) {
VendorTagDescriptor::clearGlobalVendorTagDescriptor();
- ALOGE("%s: Failed to setup vendor tag descriptors: %s",
- __FUNCTION__, res.toString8().string());
+ ALOGE("%s: Failed to setup vendor tag descriptors: %s", __FUNCTION__,
+ res.toString8().c_str());
return res.serviceSpecificErrorCode();
}
if (0 < desc->getTagCount()) {
@@ -971,8 +971,8 @@
return OK;
} else if (!res.isOk()) {
VendorTagDescriptorCache::clearGlobalVendorTagCache();
- ALOGE("%s: Failed to setup vendor tag cache: %s",
- __FUNCTION__, res.toString8().string());
+ ALOGE("%s: Failed to setup vendor tag cache: %s", __FUNCTION__,
+ res.toString8().c_str());
return res.serviceSpecificErrorCode();
}
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index c947fba..30e546c 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -1543,7 +1543,8 @@
String8 captureTime = nativeContext->getCaptureTime();
if (writer->addEntry(TAG_DATETIME, NativeContext::DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
+ reinterpret_cast<const uint8_t*>(captureTime.c_str()),
+ TIFF_IFD_0) != OK) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Invalid metadata for tag %x", TAG_DATETIME);
return nullptr;
@@ -1551,7 +1552,8 @@
// datetime original
if (writer->addEntry(TAG_DATETIMEORIGINAL, NativeContext::DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime.string()), TIFF_IFD_0) != OK) {
+ reinterpret_cast<const uint8_t*>(captureTime.c_str()),
+ TIFF_IFD_0) != OK) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
return nullptr;
@@ -1879,8 +1881,10 @@
cameraModel += brand.c_str();
BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
- reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
- TAG_UNIQUECAMERAMODEL, writer);
+ reinterpret_cast<const uint8_t*>(
+ cameraModel.c_str()),
+ TIFF_IFD_0),
+ env, TAG_UNIQUECAMERAMODEL, writer);
}
{
@@ -2165,7 +2169,8 @@
String8 description = nativeContext->getDescription();
size_t len = description.bytes() + 1;
if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
- reinterpret_cast<const uint8_t*>(description.string()), TIFF_IFD_0) != OK) {
+ reinterpret_cast<const uint8_t*>(description.c_str()),
+ TIFF_IFD_0) != OK) {
jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
"Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
}
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index 4c4443f..0e3c510 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -285,7 +285,7 @@
hardware::Parcel *parcel =
JHwParcel::GetNativeContext(env, thiz)->getParcel();
- status_t err = parcel->writeInterfaceToken(nameCopy.string());
+ status_t err = parcel->writeInterfaceToken(nameCopy.c_str());
signalExceptionForError(env, err);
}
}
@@ -687,9 +687,7 @@
static jstring MakeStringObjFromHidlString(JNIEnv *env, const hidl_string &s) {
String16 utf16String(s.c_str(), s.size());
- return env->NewString(
- reinterpret_cast<const jchar *>(utf16String.string()),
- utf16String.size());
+ return env->NewString(reinterpret_cast<const jchar *>(utf16String.c_str()), utf16String.size());
}
static jstring JHwParcel_native_readString(JNIEnv *env, jobject thiz) {
diff --git a/core/jni/android_os_UEventObserver.cpp b/core/jni/android_os_UEventObserver.cpp
index eda5075..43a8be1 100644
--- a/core/jni/android_os_UEventObserver.cpp
+++ b/core/jni/android_os_UEventObserver.cpp
@@ -51,8 +51,8 @@
const char* field = buffer;
const char* end = buffer + length + 1;
do {
- if (strstr(field, match.string())) {
- ALOGV("Matched uevent message with pattern: %s", match.string());
+ if (strstr(field, match.c_str())) {
+ ALOGV("Matched uevent message with pattern: %s", match.c_str());
return true;
}
field += strlen(field) + 1;
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 9dce5e3..6ed0a8a 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -953,8 +953,10 @@
String8 msg;
msg.appendFormat("Unknown binder error code. 0x%" PRIx32, err);
// RemoteException is a checked exception, only throw from certain methods.
- jniThrowException(env, canThrowRemoteException
- ? "android/os/RemoteException" : "java/lang/RuntimeException", msg.string());
+ jniThrowException(env,
+ canThrowRemoteException ? "android/os/RemoteException"
+ : "java/lang/RuntimeException",
+ msg.c_str());
break;
}
}
@@ -1286,8 +1288,7 @@
IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target != NULL) {
const String16& desc = target->getInterfaceDescriptor();
- return env->NewString(reinterpret_cast<const jchar*>(desc.string()),
- desc.size());
+ return env->NewString(reinterpret_cast<const jchar*>(desc.c_str()), desc.size());
}
jniThrowException(env, "java/lang/RuntimeException",
"No binder found for object");
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 4f2bf4a..18c60a7 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -148,7 +148,7 @@
const size_t N = name8.size();
if (N > 0) {
- const char* str = name8.string();
+ const char* str = name8.c_str();
for (size_t i=0; i<N; i++) {
if (str[i] < '0' || str[i] > '9') {
struct passwd* pwd = getpwnam(str);
@@ -180,7 +180,7 @@
const size_t N = name8.size();
if (N > 0) {
- const char* str = name8.string();
+ const char* str = name8.c_str();
for (size_t i=0; i<N; i++) {
if (str[i] < '0' || str[i] > '9') {
struct group* grp = getgrnam(str);
@@ -583,8 +583,8 @@
env->ReleaseStringCritical(name, str);
}
- if (!name8.isEmpty()) {
- AndroidRuntime::getRuntime()->setArgv0(name8.string(), true /* setProcName */);
+ if (!name8.empty()) {
+ AndroidRuntime::getRuntime()->setArgv0(name8.c_str(), true /* setProcName */);
}
}
@@ -690,7 +690,7 @@
return;
}
- int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
+ int fd = open(file.c_str(), O_RDONLY | O_CLOEXEC);
if (fd >= 0) {
//ALOGI("Clearing %" PRId32 " sizes", count);
@@ -704,7 +704,7 @@
close(fd);
if (len < 0) {
- ALOGW("Unable to read %s", file.string());
+ ALOGW("Unable to read %s", file.c_str());
len = 0;
}
buffer[len] = 0;
@@ -717,7 +717,7 @@
//ALOGI("Parsing at: %s", p);
for (i=0; i<count; i++) {
const String8& field = fields[i];
- if (strncmp(p, field.string(), field.length()) == 0) {
+ if (strncmp(p, field.c_str(), field.length()) == 0) {
p += field.length();
while (*p == ' ' || *p == '\t') p++;
char* num = p;
@@ -729,7 +729,7 @@
}
char* end;
sizesArray[i] = strtoll(num, &end, 10);
- //ALOGI("Field %s = %" PRId64, field.string(), sizesArray[i]);
+ // ALOGI("Field %s = %" PRId64, field.c_str(), sizesArray[i]);
foundCount++;
break;
}
@@ -746,7 +746,7 @@
free(buffer);
} else {
- ALOGW("Unable to open %s", file.string());
+ ALOGW("Unable to open %s", file.c_str());
}
//ALOGI("Done!");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd1a499..d898a23 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2094,6 +2094,16 @@
<permission android:name="android.permission.MANAGE_TEST_NETWORKS"
android:protectionLevel="signature" />
+ <!-- Allows direct access to the <RemoteAuth>Service interfaces.
+ @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <permission android:name="android.permission.MANAGE_REMOTE_AUTH"
+ android:protectionLevel="signature" />
+
+ <!-- Allows direct access to the <RemoteAuth>Service authentication methods.
+ @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) -->
+ <permission android:name="android.permission.USE_REMOTE_AUTH"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi @hide Allows applications to read Wi-Fi credential.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_WIFI_CREDENTIAL"
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e67ea82..a6830a6 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -552,10 +552,6 @@
<color name="accessibility_magnification_thumbnail_container_background_color">#99000000</color>
<color name="accessibility_magnification_thumbnail_container_stroke_color">#FFFFFF</color>
- <!-- Color of camera light when camera is in use -->
- <color name="camera_privacy_light_day">#FFFFFF</color>
- <color name="camera_privacy_light_night">#FFFFFF</color>
-
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#202124</color>
<color name="language_picker_item_text_color_secondary">#5F6368</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0ea5b8a..a72f779 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3487,7 +3487,8 @@
<!-- Whether this device prefers to show snapshot or splash screen on back predict target.
When set true, there will create windowless starting surface for the preview target, so it
won't affect activity's lifecycle. This should only be disabled on low-ram device. -->
- <bool name="config_predictShowStartingSurface">true</bool>
+ <!-- TODO(b/268563842) enable once activity snapshot is ready -->
+ <bool name="config_predictShowStartingSurface">false</bool>
<!-- default window ShowCircularMask property -->
<bool name="config_windowShowCircularMask">false</bool>
@@ -6534,8 +6535,19 @@
<!-- Interval in milliseconds to average light sensor values for camera light brightness -->
<integer name="config_cameraPrivacyLightAlsAveragingIntervalMillis">3000</integer>
- <!-- Light sensor's lux value to use as the threshold between using day or night brightness -->
- <integer name="config_cameraPrivacyLightAlsNightThreshold">4</integer>
+ <!-- Ambient Light sensor's lux values to use as the threshold between brightness colors defined
+ by config_cameraPrivacyLightColors. If the ambient brightness less than the first element
+ in this array then lights of type "camera" will be set to the color in position 0 of
+ config_cameraPrivacyLightColors. This array must be strictly increasing and have a length
+ of zero means there is only one brightness -->
+ <integer-array name="config_cameraPrivacyLightAlsLuxThresholds">
+ </integer-array>
+ <!-- Colors to configure the camera privacy light at different brightnesses. This array must
+ have exactly one more entry than config_cameraPrivacyLightAlsLuxThresholds,
+ or a length of zero if the feature isn't supported. If nonempty and the device doesn't have
+ an ambient light sensor the last element in this array will be the only one used -->
+ <array name="config_cameraPrivacyLightColors">
+ </array>
<!-- List of system components which are allowed to receive ServiceState entries in an
un-sanitized form, even if the location toggle is off. This is intended ONLY for system
diff --git a/core/res/res/values/config_tv_external_input_logging.xml b/core/res/res/values/config_tv_external_input_logging.xml
new file mode 100644
index 0000000..72e30be
--- /dev/null
+++ b/core/res/res/values/config_tv_external_input_logging.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright (C) 2023 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+ for different hardware and product builds. Do not translate.
+
+ NOTE: The naming convention is "config_camelCaseValue". Some legacy
+ entries do not follow the convention, but all new entries should. -->
+
+<resources>
+ <bool name="config_tvExternalInputLoggingDisplayNameFilterEnabled">false</bool>
+
+ <string-array name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames">
+ <item>Chromecast</item>
+ <item>Chromecast HD</item>
+ <item>SHIELD</item>
+ <item>Roku</item>
+ <item>Roku Express 4</item>
+ <item>Home Theater</item>
+ <item>Fire TV Stick</item>
+ <item>PlayStation 5</item>
+ <item>NintendoSwitch</item>
+ </string-array>
+
+ <string-array name="config_tvExternalInputLoggingDeviceBrandNames">
+ <item>Chromecast</item>
+ <item>SHIELD</item>
+ <item>Roku</item>
+ <item>Apple</item>
+ <item>Fire TV</item>
+ <item>PlayStation</item>
+ <item>Nintendo</item>
+ </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 889901a..ee0563b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5009,10 +5009,9 @@
<java-symbol type="string" name="vdm_camera_access_denied" />
<java-symbol type="string" name="vdm_secure_window" />
- <java-symbol type="color" name="camera_privacy_light_day"/>
- <java-symbol type="color" name="camera_privacy_light_night"/>
<java-symbol type="integer" name="config_cameraPrivacyLightAlsAveragingIntervalMillis"/>
- <java-symbol type="integer" name="config_cameraPrivacyLightAlsNightThreshold"/>
+ <java-symbol type="array" name="config_cameraPrivacyLightAlsLuxThresholds"/>
+ <java-symbol type="array" name="config_cameraPrivacyLightColors"/>
<java-symbol type="bool" name="config_bg_current_drain_monitor_enabled" />
<java-symbol type="array" name="config_bg_current_drain_threshold_to_restricted_bucket" />
@@ -5221,4 +5220,9 @@
<!-- Whether we order unlocking and waking -->
<java-symbol type="bool" name="config_orderUnlockAndWake" />
+
+ <!-- External TV Input Logging Configs -->
+ <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" />
+ <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" />
+ <java-symbol type="array" name="config_tvExternalInputLoggingDeviceBrandNames" />
</resources>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index a358c4f..20d8d91 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,8 +86,8 @@
<uses-permission android:name="android.permission.TEST_GRANTED" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
- <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" android:maxSdkVersion="34"/>
+ <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" android:maxSdkVersion="34"/>
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
diff --git a/core/tests/coretests/res/layout/viewgroup_test.xml b/core/tests/coretests/res/layout/viewgroup_test.xml
index 04f4f52..9b57047 100644
--- a/core/tests/coretests/res/layout/viewgroup_test.xml
+++ b/core/tests/coretests/res/layout/viewgroup_test.xml
@@ -42,8 +42,8 @@
android:id="@+id/view_translate"
android:layout_width="20dp"
android:layout_height="10dp"
- android:translationX="10dp"
- android:translationY="20dp"
+ android:translationX="10px"
+ android:translationY="20px"
android:text="Hello World!"
android:background="#2F00FF00" />
<FrameLayout
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
index 6189914..fe30d81 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -16,8 +16,6 @@
package com.android.internal.app.procstats;
-import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -108,7 +106,8 @@
ProcessState processState =
processStats.getProcessStateLocked(
APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
- processState.setCombinedState(STATE_TOP, NOW_MS);
+ processState.setState(ActivityManager.PROCESS_STATE_TOP, ProcessStats.ADJ_MEM_FACTOR_NORMAL,
+ NOW_MS, /* pkgList */ null);
processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
verify(mStatsEventOutput)
@@ -158,6 +157,38 @@
}
@SmallTest
+ public void testDumpFrozenDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ ProcessStats.ADJ_MEM_FACTOR_NORMAL, NOW_MS, /* pkgList */ null);
+ processState.onProcessFrozen(NOW_MS + 1 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+ /* pkgList */ null);
+ processState.onProcessUnfrozen(NOW_MS + 2 * TimeUnit.SECONDS.toMillis(DURATION_SECS),
+ /* pkgList */ null);
+ processState.commitStateTime(NOW_MS + 3 * TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(2 * DURATION_SECS), // bound_fgs
+ eq(0),
+ eq(0),
+ eq(DURATION_SECS), // frozen
+ eq(0));
+ }
+
+ @SmallTest
public void testDumpProcessAssociation() throws Exception {
ProcessStats processStats = new ProcessStats();
AssociationState associationState =
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index 246a1e7..a0e9947 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -48,7 +48,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -102,6 +104,25 @@
return partitionOrder.toString();
}
+ // configIndex should come from real time partition order cause partitions could get
+ // reordered by /product/overlay/partition_order.xml
+ private Map<String, Integer> createConfigIndexes(OverlayConfig overlayConfig,
+ String... configPartitions) {
+ Map<String, Integer> configIndexes = new HashMap<>();
+ for (int i = 0; i < configPartitions.length; i++) {
+ configIndexes.put(configPartitions[i], -1);
+ }
+
+ String[] partitions = overlayConfig.getPartitionOrder().split(", ");
+ int index = 0;
+ for (int i = 0; i < partitions.length; i++) {
+ if (configIndexes.containsKey(partitions[i])) {
+ configIndexes.put(partitions[i], index++);
+ }
+ }
+ return configIndexes;
+ }
+
@Test
public void testImmutableAfterNonImmutableFails() throws IOException {
mExpectedException.expect(IllegalStateException.class);
@@ -292,11 +313,13 @@
mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five");
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", true, true, 0);
- assertConfig(overlayConfig, "two", true, true, 1);
- assertConfig(overlayConfig, "three", true, true, 2);
- assertConfig(overlayConfig, "four", true, true, 3);
- assertConfig(overlayConfig, "five", true, true, 4);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "oem", "product", "system_ext");
+ assertConfig(overlayConfig, "one", true, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", true, true, configIndexes.get("oem"));
+ assertConfig(overlayConfig, "four", true, true, configIndexes.get("product"));
+ assertConfig(overlayConfig, "five", true, true, configIndexes.get("system_ext"));
}
@Test
@@ -313,9 +336,11 @@
true, 0);
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", false, true, 0);
- assertConfig(overlayConfig, "two", true, true, 1);
- assertConfig(overlayConfig, "three", false, true, 2);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "product");
+ assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", true, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
}
@Test
@@ -327,9 +352,11 @@
true, 0);
final OverlayConfig overlayConfig = createConfigImpl();
- assertConfig(overlayConfig, "one", false, true, 0);
- assertConfig(overlayConfig, "two", false, true, 1);
- assertConfig(overlayConfig, "three", false, true, 2);
+ Map<String, Integer> configIndexes = createConfigIndexes(overlayConfig,
+ "vendor", "odm", "product");
+ assertConfig(overlayConfig, "one", false, true, configIndexes.get("vendor"));
+ assertConfig(overlayConfig, "two", false, true, configIndexes.get("odm"));
+ assertConfig(overlayConfig, "three", false, true, configIndexes.get("product"));
}
@Test
diff --git a/data/keyboards/Vendor_18d1_Product_9451.idc b/data/keyboards/Vendor_18d1_Product_9451.idc
index e13fcb2..07cfc79 100644
--- a/data/keyboards/Vendor_18d1_Product_9451.idc
+++ b/data/keyboards/Vendor_18d1_Product_9451.idc
@@ -13,11 +13,10 @@
# limitations under the License.
#
-# Input Device Configuration file for a flavor of the Google Reference RCU Remote.
+# Input Device Configuration file for a flavor of Google Remote Control.
#
#
# Basic Parameters
-keyboard.layout = Vendor_0957_Product_0001
keyboard.doNotWakeByDefault = 1
-audio.mic = 1
\ No newline at end of file
+audio.mic = 1
diff --git a/data/keyboards/Vendor_18d1_Product_9451.kl b/data/keyboards/Vendor_18d1_Product_9451.kl
new file mode 100644
index 0000000..fb425be
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_9451.kl
@@ -0,0 +1,39 @@
+# Copyright 2023 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.
+
+#
+# Key Layout file for flavor of Google Remote Control.
+#
+
+key 116 POWER WAKE
+key 217 ASSIST WAKE
+
+key 103 DPAD_UP
+key 108 DPAD_DOWN
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+key 353 DPAD_CENTER
+
+key 158 BACK
+key 172 HOME WAKE
+
+key 113 VOLUME_MUTE
+key 114 VOLUME_DOWN
+key 115 VOLUME_UP
+
+# custom keys
+key usage 0x000c0186 MACRO_1 WAKE
+
+key usage 0x000c0077 BUTTON_3 WAKE #YouTube
+key usage 0x000c0078 BUTTON_4 WAKE #Netflix
diff --git a/drm/jni/android_drm_DrmManagerClient.cpp b/drm/jni/android_drm_DrmManagerClient.cpp
index e2bb6a6..14d5eaf 100644
--- a/drm/jni/android_drm_DrmManagerClient.cpp
+++ b/drm/jni/android_drm_DrmManagerClient.cpp
@@ -167,8 +167,8 @@
jint uniqueId = event.getUniqueId();
jint type = event.getType();
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jstring message = env->NewStringUTF(event.getMessage().string());
- ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().string());
+ jstring message = env->NewStringUTF(event.getMessage().c_str());
+ ALOGV("JNIOnInfoListener::onInfo => %d | %d | %s", uniqueId, type, event.getMessage().c_str());
env->CallStaticVoidMethod(
mClass,
@@ -273,15 +273,15 @@
const char* value = pConstraints->getAsByteArray(&key);
if (NULL != value) {
ScopedLocalRef<jbyteArray> dataArray(env, env->NewByteArray(strlen(value)));
- ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
+ ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
env->SetByteArrayRegion(dataArray.get(), 0, strlen(value), (jbyte*)value);
env->CallVoidMethod(constraints, ContentValues_putByteArray,
keyString.get(), dataArray.get());
}
} else {
String8 value = pConstraints->get(key);
- ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
- ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+ ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+ ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
env->CallVoidMethod(constraints, ContentValues_putString,
keyString.get(), valueString.get());
}
@@ -320,8 +320,8 @@
// insert the entry<constraintKey, constraintValue>
// to newly created java object
String8 value = pMetadata->get(key);
- ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
- ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+ ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+ ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
env->CallVoidMethod(metadata, ContentValues_putString,
keyString.get(), valueString.get());
}
@@ -357,19 +357,19 @@
env->CallVoidMethod(
drmSupportInfo, env->GetMethodID(clazz, "setDescription", "(Ljava/lang/String;)V"),
- env->NewStringUTF(info.getDescription().string()));
+ env->NewStringUTF(info.getDescription().c_str()));
DrmSupportInfo::MimeTypeIterator iterator = info.getMimeTypeIterator();
while (iterator.hasNext()) {
String8 value = iterator.next();
- env->CallVoidMethod(drmSupportInfo, addMimeTypeId, env->NewStringUTF(value.string()));
+ env->CallVoidMethod(drmSupportInfo, addMimeTypeId, env->NewStringUTF(value.c_str()));
}
DrmSupportInfo::FileSuffixIterator it = info.getFileSuffixIterator();
while (it.hasNext()) {
String8 value = it.next();
env->CallVoidMethod(
- drmSupportInfo, addFileSuffixId, env->NewStringUTF(value.string()));
+ drmSupportInfo, addFileSuffixId, env->NewStringUTF(value.c_str()));
}
env->SetObjectArrayElement(array, i, drmSupportInfo);
@@ -459,7 +459,7 @@
String8 keyString = Utility::getStringValue(env, key.get());
String8 valueString = Utility::getStringValue(env, valString.get());
- ALOGV("Key: %s | Value: %s", keyString.string(), valueString.string());
+ ALOGV("Key: %s | Value: %s", keyString.c_str(), valueString.c_str());
drmInfo.put(keyString, valueString);
}
@@ -488,15 +488,15 @@
jmethodID constructorId
= env->GetMethodID(clazz, "<init>", "([BLjava/lang/String;Ljava/lang/String;)V");
jobject processedData = env->NewObject(clazz, constructorId, dataArray,
- env->NewStringUTF((drmInfo.get(DrmInfoRequest::ACCOUNT_ID)).string()),
- env->NewStringUTF((drmInfo.get(DrmInfoRequest::SUBSCRIPTION_ID)).string()));
+ env->NewStringUTF((drmInfo.get(DrmInfoRequest::ACCOUNT_ID)).c_str()),
+ env->NewStringUTF((drmInfo.get(DrmInfoRequest::SUBSCRIPTION_ID)).c_str()));
constructorId
= env->GetMethodID(localRef,
"<init>", "(IILandroid/drm/ProcessedData;Ljava/lang/String;)V");
drmInfoStatus = env->NewObject(localRef, constructorId, statusCode, infoType,
- processedData, env->NewStringUTF(pDrmInfoStatus->mimeType.string()));
+ processedData, env->NewStringUTF(pDrmInfoStatus->mimeType.c_str()));
}
delete[] mData; mData = NULL;
@@ -533,7 +533,7 @@
String8 keyString = Utility::getStringValue(env, key.get());
String8 valueString = Utility::getStringValue(env, value.get());
- ALOGV("Key: %s | Value: %s", keyString.string(), valueString.string());
+ ALOGV("Key: %s | Value: %s", keyString.c_str(), valueString.c_str());
drmInfoReq.put(keyString, valueString);
}
@@ -554,7 +554,7 @@
drmInfoObject
= env->NewObject(localRef,
env->GetMethodID(localRef, "<init>", "(I[BLjava/lang/String;)V"),
- mInfoType, dataArray, env->NewStringUTF(pDrmInfo->getMimeType().string()));
+ mInfoType, dataArray, env->NewStringUTF(pDrmInfo->getMimeType().c_str()));
DrmInfo::KeyIterator it = pDrmInfo->keyIterator();
jmethodID putMethodId
@@ -563,8 +563,8 @@
while (it.hasNext()) {
String8 key = it.next();
String8 value = pDrmInfo->get(key);
- ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.string()));
- ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.string()));
+ ScopedLocalRef<jstring> keyString(env, env->NewStringUTF(key.c_str()));
+ ScopedLocalRef<jstring> valueString(env, env->NewStringUTF(value.c_str()));
env->CallVoidMethod(drmInfoObject, putMethodId,
keyString.get(), valueString.get());
}
@@ -602,7 +602,7 @@
->getOriginalMimeType(uniqueId,
Utility::getStringValue(env, path), fd);
ALOGV("getOriginalMimeType Exit");
- return env->NewStringUTF(mimeType.string());
+ return env->NewStringUTF(mimeType.c_str());
}
static jint android_drm_DrmManagerClient_checkRightsStatus(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 55eabb0..c3d8f9a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -16,12 +16,14 @@
package androidx.window.extensions;
+import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
import android.window.TaskFragmentOrganizer;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.RawFoldingFeatureProducer;
import androidx.window.extensions.area.WindowAreaComponent;
@@ -111,9 +113,13 @@
* {@link WindowExtensions#getWindowLayoutComponent()}.
* @return {@link ActivityEmbeddingComponent} OEM implementation.
*/
- @NonNull
+ @Nullable
public ActivityEmbeddingComponent getActivityEmbeddingComponent() {
if (mSplitController == null) {
+ if (!ActivityTaskManager.supportsMultiWindow(getApplication())) {
+ // Disable AE for device that doesn't support multi window.
+ return null;
+ }
synchronized (mLock) {
if (mSplitController == null) {
mSplitController = new SplitController(
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 896fe61..d894487 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -651,7 +651,8 @@
if (minDimensionsPair == null) {
return splitAttributes;
}
- final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+ taskProperties, splitAttributes);
final Configuration taskConfiguration = taskProperties.getConfiguration();
final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
foldingFeature);
@@ -726,7 +727,8 @@
Rect getRelBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
@NonNull SplitAttributes splitAttributes) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+ final FoldingFeature foldingFeature = getFoldingFeatureForHingeType(
+ taskProperties, splitAttributes);
if (!shouldShowSplit(splitAttributes)) {
return new Rect();
}
@@ -933,6 +935,17 @@
}
@Nullable
+ private FoldingFeature getFoldingFeatureForHingeType(
+ @NonNull TaskProperties taskProperties,
+ @NonNull SplitAttributes splitAttributes) {
+ SplitType splitType = splitAttributes.getSplitType();
+ if (!(splitType instanceof HingeSplitType)) {
+ return null;
+ }
+ return getFoldingFeature(taskProperties);
+ }
+
+ @Nullable
@VisibleForTesting
FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
final int displayId = taskProperties.getDisplayId();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index ccf9552..e03e1ec 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -319,13 +319,17 @@
return features;
}
+ // We will transform the feature bounds to the Activity window, so using the rotation
+ // from the same source (WindowConfiguration) to make sure they are synchronized.
+ final int rotation = windowConfiguration.getDisplayRotation();
+
for (CommonFoldingFeature baseFeature : storedFeatures) {
Integer state = convertToExtensionState(baseFeature.getState());
if (state == null) {
continue;
}
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(windowConfiguration, featureRect);
if (isZero(featureRect)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 5bfb0ebd..15a329bd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -120,10 +120,12 @@
}
List<SidecarDisplayFeature> features = new ArrayList<>();
+ final int rotation = activity.getResources().getConfiguration().windowConfiguration
+ .getDisplayRotation();
for (CommonFoldingFeature baseFeature : mStoredFeatures) {
SidecarDisplayFeature feature = new SidecarDisplayFeature();
Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
+ rotateRectToDisplayRotation(displayId, rotation, featureRect);
transformToWindowSpaceRect(activity, featureRect);
feature.setRect(featureRect);
feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 9e2611f..57f8308 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -16,8 +16,6 @@
package androidx.window.util;
-import static android.view.Surface.ROTATION_0;
-import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -25,8 +23,8 @@
import android.content.Context;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
+import android.util.RotationUtils;
import android.view.DisplayInfo;
-import android.view.Surface;
import android.view.WindowManager;
import androidx.annotation.NonNull;
@@ -45,10 +43,9 @@
* Rotates the input rectangle specified in default display orientation to the current display
* rotation.
*/
- public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
+ public static void rotateRectToDisplayRotation(int displayId, int rotation, Rect inOutRect) {
DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
- int rotation = displayInfo.rotation;
boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270;
int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
@@ -56,35 +53,7 @@
inOutRect.intersect(0, 0, displayWidth, displayHeight);
- rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
- }
-
- /**
- * Rotates the input rectangle within parent bounds for a given delta.
- */
- private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
- @Surface.Rotation int delta) {
- int origLeft = inOutRect.left;
- switch (delta) {
- case ROTATION_0:
- return;
- case ROTATION_90:
- inOutRect.left = inOutRect.top;
- inOutRect.top = parentWidth - inOutRect.right;
- inOutRect.right = inOutRect.bottom;
- inOutRect.bottom = parentWidth - origLeft;
- return;
- case ROTATION_180:
- inOutRect.left = parentWidth - inOutRect.right;
- inOutRect.right = parentWidth - origLeft;
- return;
- case ROTATION_270:
- inOutRect.left = parentHeight - inOutRect.bottom;
- inOutRect.bottom = inOutRect.right;
- inOutRect.right = parentHeight - inOutRect.top;
- inOutRect.top = origLeft;
- return;
- }
+ RotationUtils.rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
}
/** Transforms rectangle from absolute coordinate space to the window coordinate space. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index d189ae2..45564cb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -16,8 +16,11 @@
package androidx.window.extensions;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.google.common.truth.Truth.assertThat;
+import android.app.ActivityTaskManager;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,7 +55,11 @@
@Test
public void testGetActivityEmbeddingComponent() {
- assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ if (ActivityTaskManager.supportsMultiWindow(getInstrumentation().getContext())) {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
+ } else {
+ assertThat(mExtensions.getActivityEmbeddingComponent()).isNull();
+ }
}
@Test
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index d0c0c02..7edf2fc 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -450,6 +450,10 @@
<dimen name="freeform_resize_corner">44dp</dimen>
+ <!-- The width of the area at the sides of the screen where a freeform task will transition to
+ split select if dragged until the touch input is within the range. -->
+ <dimen name="desktop_mode_transition_area_width">32dp</dimen>
+
<!-- The height of the area at the top of the screen where a freeform task will transition to
fullscreen if dragged until the top bound of the task is within the area. -->
<dimen name="desktop_mode_transition_area_height">16dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 8400dde..645a961 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.bubbles;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -115,6 +114,7 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskView;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -143,6 +143,8 @@
// Should match with PhoneWindowManager
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
+ private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";
/**
* Common interface to send updates to bubble views.
@@ -182,6 +184,7 @@
private final ShellTaskOrganizer mTaskOrganizer;
private final DisplayController mDisplayController;
private final TaskViewTransitions mTaskViewTransitions;
+ private final Transitions mTransitions;
private final SyncTransactionQueue mSyncQueue;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
@@ -282,6 +285,7 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
BubbleProperties bubbleProperties) {
@@ -317,6 +321,7 @@
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
+ mTransitions = transitions;
mOneHandedOptional = oneHandedOptional;
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
@@ -416,23 +421,9 @@
}
}, mMainHandler);
- mTaskStackListener.addListener(new TaskStackListenerCallback() {
- @Override
- public void onTaskMovedToFront(int taskId) {
- mMainExecutor.execute(() -> {
- int expandedId = INVALID_TASK_ID;
- if (mStackView != null && mStackView.getExpandedBubble() != null
- && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mStackView.isSwitchAnimating()) {
- expandedId = mStackView.getExpandedBubble().getTaskId();
- }
- if (expandedId != INVALID_TASK_ID && expandedId != taskId) {
- mBubbleData.setExpanded(false);
- }
- });
- }
+ mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData));
+ mTaskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
@@ -883,8 +874,10 @@
String action = intent.getAction();
String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
- if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
- && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason))
+ boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason)
+ || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)
+ || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason);
+ if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse)
|| Intent.ACTION_SCREEN_OFF.equals(action)) {
mMainExecutor.execute(() -> collapseStack());
}
@@ -1961,6 +1954,15 @@
}
}
+ /**
+ * Returns whether the stack is animating or not.
+ */
+ public boolean isStackAnimating() {
+ return mStackView != null
+ && (mStackView.isExpansionAnimating()
+ || mStackView.isSwitchAnimating());
+ }
+
@VisibleForTesting
@Nullable
public BubbleStackView getStackView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
new file mode 100644
index 0000000..9e8a385
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * Observer used to identify tasks that are opening or moving to front. If a bubble activity is
+ * currently opened when this happens, we'll collapse the bubbles.
+ */
+public class BubblesTransitionObserver implements Transitions.TransitionObserver {
+
+ private BubbleController mBubbleController;
+ private BubbleData mBubbleData;
+
+ public BubblesTransitionObserver(BubbleController controller,
+ BubbleData bubbleData) {
+ mBubbleController = controller;
+ mBubbleData = bubbleData;
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // We only care about opens / move to fronts when bubbles are expanded & not animating.
+ if (taskInfo == null
+ || taskInfo.taskId == INVALID_TASK_ID
+ || !TransitionUtil.isOpeningType(change.getMode())
+ || mBubbleController.isStackAnimating()
+ || !mBubbleData.isExpanded()
+ || mBubbleData.getSelectedBubble() == null) {
+ continue;
+ }
+ int expandedId = mBubbleData.getSelectedBubble().getTaskId();
+ // If the task id that's opening is the same as the expanded bubble, skip collapsing
+ // because it is our bubble that is opening.
+ if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
+ mBubbleData.setExpanded(false);
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {
+
+ }
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+
+ }
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
+
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1c2cee5..998cd5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -72,7 +72,6 @@
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
import com.android.wm.shell.desktopmode.DesktopMode;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -109,13 +108,13 @@
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import java.util.Optional;
+
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import java.util.Optional;
-
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -800,30 +799,10 @@
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController) {
- if (DesktopModeStatus.isProto2Enabled()) {
- return desktopTasksController.map(DesktopTasksController::asDesktopMode);
- }
- return desktopModeController.map(DesktopModeController::asDesktopMode);
+ return desktopTasksController.map(DesktopTasksController::asDesktopMode);
}
- @BindsOptionalOf
- @DynamicOverride
- abstract DesktopModeController optionalDesktopModeController();
-
- @WMSingleton
- @Provides
- static Optional<DesktopModeController> provideDesktopModeController(
- @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
- // Use optional-of-lazy for the dependency that this provider relies on.
- // Lazy ensures that this provider will not be the cause the dependency is created
- // when it will not be returned due to the condition below.
- if (DesktopModeStatus.isProto1Enabled()) {
- return desktopModeController.map(Lazy::get);
- }
- return Optional.empty();
- }
@BindsOptionalOf
@DynamicOverride
@@ -836,7 +815,7 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
return desktopTasksController.map(Lazy::get);
}
return Optional.empty();
@@ -853,7 +832,7 @@
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
return desktopModeTaskRepository.map(Lazy::get);
}
return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 93ce91f..e9f3e1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -54,7 +54,6 @@
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -167,6 +166,7 @@
@ShellMainThread Handler mainHandler,
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
@@ -176,7 +176,8 @@
statusBarService, windowManager, windowManagerShellWrapper, userManager,
launcherApps, logger, taskStackListener, organizer, positioner, displayController,
oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE);
+ taskViewTransitions, transitions, syncQueue, wmService,
+ ProdBubbleProperties.INSTANCE);
}
//
@@ -195,10 +196,9 @@
ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
return new DesktopModeWindowDecorViewModel(
context,
mainHandler,
@@ -209,7 +209,6 @@
shellController,
syncQueue,
transitions,
- desktopModeController,
desktopTasksController,
rootTaskDisplayAreaOrganizer);
}
@@ -351,13 +350,12 @@
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
KeyguardTransitionHandler keyguardTransitionHandler,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<UnfoldTransitionHandler> unfoldHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
pipTransitionController, recentsTransitionHandler,
- keyguardTransitionHandler, desktopModeController, desktopTasksController,
+ keyguardTransitionHandler, desktopTasksController,
unfoldHandler);
}
@@ -469,24 +467,6 @@
@WMSingleton
@Provides
@DynamicOverride
- static DesktopModeController provideDesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
- @ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
- return new DesktopModeController(context, shellInit, shellController, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
- mainExecutor);
- }
-
- @WMSingleton
- @Provides
- @DynamicOverride
static DesktopTasksController provideDesktopTasksController(
Context context,
ShellInit shellInit,
@@ -551,8 +531,7 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- DefaultMixedHandler defaultMixedHandler,
- Optional<DesktopModeController> desktopModeController) {
+ DefaultMixedHandler defaultMixedHandler) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
deleted file mode 100644
index 5b24d7a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright (C) 2022 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.wm.shell.desktopmode;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.database.ContentObserver;
-import android.graphics.Region;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.ArraySet;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ExternalInterfaceBinder;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Handles windowing changes when desktop mode system setting changes
- */
-public class DesktopModeController implements RemoteCallable<DesktopModeController>,
- Transitions.TransitionHandler {
-
- private final Context mContext;
- private final ShellController mShellController;
- private final ShellTaskOrganizer mShellTaskOrganizer;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- private final Transitions mTransitions;
- private final DesktopModeTaskRepository mDesktopModeTaskRepository;
- private final ShellExecutor mMainExecutor;
- private final DesktopModeImpl mDesktopModeImpl = new DesktopModeImpl();
- private final SettingsObserver mSettingsObserver;
-
- public DesktopModeController(Context context,
- ShellInit shellInit,
- ShellController shellController,
- ShellTaskOrganizer shellTaskOrganizer,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Transitions transitions,
- DesktopModeTaskRepository desktopModeTaskRepository,
- @ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
- mContext = context;
- mShellController = shellController;
- mShellTaskOrganizer = shellTaskOrganizer;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
- mTransitions = transitions;
- mDesktopModeTaskRepository = desktopModeTaskRepository;
- mMainExecutor = mainExecutor;
- mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- if (DesktopModeStatus.isProto1Enabled()) {
- shellInit.addInitCallback(this::onInit, this);
- }
- }
-
- private void onInit() {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_DESKTOP_MODE,
- this::createExternalInterface, this);
- mSettingsObserver.observe();
- if (DesktopModeStatus.isActive(mContext)) {
- updateDesktopModeActive(true);
- }
- mTransitions.addHandler(this);
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- /**
- * Get connection interface between sysui and shell
- */
- public DesktopMode asDesktopMode() {
- return mDesktopModeImpl;
- }
-
- /**
- * Creates a new instance of the external interface to pass to another process.
- */
- private ExternalInterfaceBinder createExternalInterface() {
- return new IDesktopModeImpl(this);
- }
-
- /**
- * Adds a listener to find out about changes in the visibility of freeform tasks.
- *
- * @param listener the listener to add.
- * @param callbackExecutor the executor to call the listener on.
- */
- public void addVisibleTasksListener(DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor);
- }
-
- /**
- * Adds a listener to track changes to corners of desktop mode tasks.
- * @param listener the listener to add.
- * @param callbackExecutor the executor to call the listener on.
- */
- public void addTaskCornerListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mDesktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor);
- }
-
- @VisibleForTesting
- void updateDesktopModeActive(boolean active) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
-
- int displayId = mContext.getDisplayId();
-
- ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level so tasks inherit it
- clearFreeformForStandardTasks(runningTasks, wct);
- if (active) {
- moveHomeBehindVisibleTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
- } else {
- clearBoundsForStandardTasks(runningTasks, wct);
- setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
- } else {
- mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
- }
- }
-
- private WindowContainerTransaction clearBoundsForStandardTasks(
- ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
- WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- }
-
- private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
- WindowContainerTransaction wct) {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
- RunningTaskInfo homeTask = null;
- ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
- for (RunningTaskInfo taskInfo : runningTasks) {
- if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
- homeTask = taskInfo;
- } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && taskInfo.isVisible()) {
- visibleTasks.add(taskInfo);
- }
- }
- if (homeTask == null) {
- ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
- } else {
- ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
- visibleTasks.size());
- wct.reorder(homeTask.getToken(), true /* onTop */);
- for (RunningTaskInfo task : visibleTasks) {
- wct.reorder(task.getToken(), true /* onTop */);
- }
- }
- }
-
- private void setDisplayAreaWindowingMode(int displayId,
- @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- displayId);
- if (displayAreaInfo == null) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE,
- "unable to update windowing mode for display %d display not found", displayId);
- return;
- }
-
- ProtoLog.v(WM_SHELL_DESKTOP_MODE,
- "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
- displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
- windowingMode);
-
- wct.setWindowingMode(displayAreaInfo.token, windowingMode);
- }
-
- /**
- * Show apps on desktop
- */
- void showDesktopApps(int displayId) {
- // Bring apps to front, ignoring their visibility status to always ensure they are on top.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- bringDesktopAppsToFront(displayId, wct);
-
- if (!wct.isEmpty()) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- // TODO(b/268662477): add animation for the transition
- mTransitions.startTransition(TRANSIT_NONE, wct, null /* handler */);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
- }
-
- /** Get number of tasks that are marked as visible */
- int getVisibleTaskCount(int displayId) {
- return mDesktopModeTaskRepository.getVisibleTaskCount(displayId);
- }
-
- private void bringDesktopAppsToFront(int displayId, WindowContainerTransaction wct) {
- final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks(displayId);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-
- final List<RunningTaskInfo> taskInfos = new ArrayList<>();
- for (Integer taskId : activeTasks) {
- RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
- if (taskInfo != null) {
- taskInfos.add(taskInfo);
- }
- }
-
- if (taskInfos.isEmpty()) {
- return;
- }
-
- moveHomeTaskToFront(wct);
-
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "bringDesktopAppsToFront: reordering all active tasks to the front");
- final List<Integer> allTasksInZOrder =
- mDesktopModeTaskRepository.getFreeformTasksInZOrder();
- // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
- // in the WCT.
- taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
- for (RunningTaskInfo task : taskInfos) {
- wct.reorder(task.token, true);
- }
- }
-
- private void moveHomeTaskToFront(WindowContainerTransaction wct) {
- for (RunningTaskInfo task : mShellTaskOrganizer.getRunningTasks(mContext.getDisplayId())) {
- if (task.getActivityType() == ACTIVITY_TYPE_HOME) {
- wct.reorder(task.token, true /* onTop */);
- return;
- }
- }
- }
-
- /**
- * Update corner rects stored for a specific task
- * @param taskId task to update
- * @param taskCorners task's new corner handles
- */
- public void onTaskCornersChanged(int taskId, Region taskCorners) {
- mDesktopModeTaskRepository.updateTaskCorners(taskId, taskCorners);
- }
-
- /**
- * Remove corners saved for a task. Likely used due to task closure.
- * @param taskId task to remove
- */
- public void removeCornersForTask(int taskId) {
- mDesktopModeTaskRepository.removeTaskCorners(taskId);
- }
-
- /**
- * Moves a specifc task to the front.
- * @param taskInfo the task to show in front.
- */
- public void moveTaskToFront(RunningTaskInfo taskInfo) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(taskInfo.token, true /* onTop */);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_TO_FRONT, wct, null);
- } else {
- mShellTaskOrganizer.applyTransaction(wct);
- }
- }
-
- /**
- * Turn desktop mode on or off
- * @param active the desired state for desktop mode setting
- */
- public void setDesktopModeActive(boolean active) {
- int value = active ? 1 : 0;
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
- }
-
- /**
- * Returns the windowing mode of the display area with the specified displayId.
- * @param displayId
- * @return
- */
- public int getDisplayAreaWindowingMode(int displayId) {
- return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
- .configuration.windowConfiguration.getWindowingMode();
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- // This handler should never be the sole handler, so should not animate anything.
- return false;
- }
-
- @Nullable
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- RunningTaskInfo triggerTask = request.getTriggerTask();
- // Only do anything if we are in desktop mode and opening/moving-to-front a task/app in
- // freeform
- if (!DesktopModeStatus.isActive(mContext)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "skip shell transition request: desktop mode not active");
- return null;
- }
- if (request.getType() != TRANSIT_OPEN && request.getType() != TRANSIT_TO_FRONT) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "skip shell transition request: unsupported type %s",
- WindowManager.transitTypeToString(request.getType()));
- return null;
- }
- if (triggerTask == null || triggerTask.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "skip shell transition request: not freeform task");
- return null;
- }
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "handle shell transition request: %s", request);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- bringDesktopAppsToFront(triggerTask.displayId, wct);
- wct.reorder(triggerTask.token, true /* onTop */);
-
- return wct;
- }
-
- /**
- * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
- * This is intended to be used when desktop mode is part of another animation but isn't, itself,
- * animating.
- */
- public void syncSurfaceState(@NonNull TransitionInfo info,
- SurfaceControl.Transaction finishTransaction) {
- // Add rounded corners to freeform windows
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.dialogCornerRadius});
- final int cornerRadius = ta.getDimensionPixelSize(0, 0);
- ta.recycle();
- for (TransitionInfo.Change change: info.getChanges()) {
- if (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- finishTransaction.setCornerRadius(change.getLeash(), cornerRadius);
- }
- }
- }
-
- /**
- * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
- */
- private final class SettingsObserver extends ContentObserver {
-
- private final Uri mDesktopModeSetting = Settings.System.getUriFor(
- Settings.System.DESKTOP_MODE);
-
- private final Context mContext;
-
- SettingsObserver(Context context, Handler handler) {
- super(handler);
- mContext = context;
- }
-
- public void observe() {
- // TODO(b/242867463): listen for setting change for all users
- mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
- false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
- }
-
- @Override
- public void onChange(boolean selfChange, @Nullable Uri uri) {
- if (mDesktopModeSetting.equals(uri)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
- desktopModeSettingChanged();
- }
- }
-
- private void desktopModeSettingChanged() {
- boolean enabled = DesktopModeStatus.isActive(mContext);
- updateDesktopModeActive(enabled);
- }
- }
-
- /**
- * The interface for calls from outside the shell, within the host process.
- */
- @ExternalThread
- private final class DesktopModeImpl implements DesktopMode {
-
- @Override
- public void addVisibleTasksListener(
- DesktopModeTaskRepository.VisibleTasksListener listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addVisibleTasksListener(listener, callbackExecutor);
- });
- }
-
- @Override
- public void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
- Executor callbackExecutor) {
- mMainExecutor.execute(() -> {
- DesktopModeController.this.addTaskCornerListener(listener, callbackExecutor);
- });
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class IDesktopModeImpl extends IDesktopMode.Stub
- implements ExternalInterfaceBinder {
-
- private DesktopModeController mController;
-
- IDesktopModeImpl(DesktopModeController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- @Override
- public void invalidate() {
- mController = null;
- }
-
- @Override
- public void showDesktopApps(int displayId) {
- executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
- controller -> controller.showDesktopApps(displayId));
- }
-
- @Override
- public void showDesktopApp(int taskId) throws RemoteException {
- // TODO
- }
-
- @Override
- public int getVisibleTaskCount(int displayId) throws RemoteException {
- int[] result = new int[1];
- executeRemoteCallWithTaskPermission(mController, "getVisibleTaskCount",
- controller -> result[0] = controller.getVisibleTaskCount(displayId),
- true /* blocking */
- );
- return result[0];
- }
-
- @Override
- public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
-
- }
-
- @Override
- public void stashDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void hideStashedDesktopApps(int displayId) throws RemoteException {
- // Stashing of desktop apps not needed. Apps always launch on desktop
- }
-
- @Override
- public void setTaskListener(IDesktopTaskListener listener) throws RemoteException {
- // TODO(b/261234402): move visibility from sysui state to listener
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 517f9f2..7783113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,14 +16,7 @@
package com.android.wm.shell.desktopmode;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-
-import android.content.Context;
import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.internal.protolog.common.ProtoLog;
/**
* Constants for desktop mode feature
@@ -31,13 +24,7 @@
public class DesktopModeStatus {
/**
- * Flag to indicate whether desktop mode is available on the device
- */
- private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode", false);
-
- /**
- * Flag to indicate whether desktop mode proto 2 is available on the device
+ * Flag to indicate whether desktop mode proto is available on the device
*/
private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_2", false);
@@ -64,28 +51,13 @@
"persist.wm.debug.desktop_stashing", false);
/**
- * Return {@code true} if desktop mode support is enabled
- */
- public static boolean isProto1Enabled() {
- return IS_SUPPORTED;
- }
-
- /**
* Return {@code true} is desktop windowing proto 2 is enabled
*/
- public static boolean isProto2Enabled() {
+ public static boolean isEnabled() {
return IS_PROTO2_ENABLED;
}
/**
- * Return {@code true} if proto 1 or 2 is enabled.
- * Can be used to guard logic that is common for both prototypes.
- */
- public static boolean isAnyEnabled() {
- return isProto1Enabled() || isProto2Enabled();
- }
-
- /**
* Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
*/
public static boolean isVeiledResizeEnabled() {
@@ -99,26 +71,4 @@
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
- /**
- * Check if desktop mode is active
- *
- * @return {@code true} if active
- */
- public static boolean isActive(Context context) {
- if (!isAnyEnabled()) {
- return false;
- }
- if (isProto2Enabled()) {
- // Desktop mode is always active in prototype 2
- return true;
- }
- try {
- int result = Settings.System.getIntForUser(context.getContentResolver(),
- Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- return result != 0;
- } catch (Exception e) {
- ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
- return false;
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 0f0d572..a587bed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
@@ -47,6 +48,15 @@
* Animated visual indicator for Desktop Mode windowing transitions.
*/
public class DesktopModeVisualIndicator {
+ public static final int INVALID_INDICATOR = -1;
+ /** Indicates impending transition into desktop mode */
+ public static final int TO_DESKTOP_INDICATOR = 1;
+ /** Indicates impending transition into fullscreen */
+ public static final int TO_FULLSCREEN_INDICATOR = 2;
+ /** Indicates impending transition into split select on the left side */
+ public static final int TO_SPLIT_LEFT_INDICATOR = 3;
+ /** Indicates impending transition into split select on the right side */
+ public static final int TO_SPLIT_RIGHT_INDICATOR = 4;
private final Context mContext;
private final DisplayController mDisplayController;
@@ -54,6 +64,7 @@
private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
private final SurfaceControl mTaskSurface;
+ private final Rect mIndicatorRange = new Rect();
private SurfaceControl mLeash;
private final SyncTransactionQueue mSyncQueue;
@@ -61,11 +72,12 @@
private View mView;
private boolean mIsFullscreen;
+ private int mType;
public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer,
- RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer, int type) {
mSyncQueue = syncQueue;
mTaskInfo = taskInfo;
mDisplayController = displayController;
@@ -73,10 +85,64 @@
mTaskSurface = taskSurface;
mTaskOrganizer = taskOrganizer;
mRootTdaOrganizer = taskDisplayAreaOrganizer;
+ mType = type;
+ defineIndicatorRange();
createView();
}
/**
+ * If an indicator is warranted based on the input and task bounds, return the type of
+ * indicator that should be created.
+ */
+ public static int determineIndicatorType(PointF inputCoordinates, Rect taskBounds,
+ DisplayLayout layout, Context context) {
+ int transitionAreaHeight = context.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionAreaWidth = context.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
+ if (taskBounds.top <= transitionAreaHeight) return TO_FULLSCREEN_INDICATOR;
+ if (inputCoordinates.x <= transitionAreaWidth) return TO_SPLIT_LEFT_INDICATOR;
+ if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
+ return TO_SPLIT_RIGHT_INDICATOR;
+ }
+ return INVALID_INDICATOR;
+ }
+
+ /**
+ * Determine range of inputs that will keep this indicator displaying.
+ */
+ private void defineIndicatorRange() {
+ DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+ int captionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.freeform_decor_caption_height);
+ int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
+ switch (mType) {
+ case TO_DESKTOP_INDICATOR:
+ // TO_DESKTOP indicator is only dismissed on release; entire display is valid.
+ mIndicatorRange.set(0, 0, layout.width(), layout.height());
+ break;
+ case TO_FULLSCREEN_INDICATOR:
+ // If drag results in caption going above the top edge of the display, we still
+ // want to transition to fullscreen.
+ mIndicatorRange.set(0, -captionHeight, layout.width(), transitionAreaHeight);
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ mIndicatorRange.set(0, transitionAreaHeight, transitionAreaWidth, layout.height());
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ mIndicatorRange.set(layout.width() - transitionAreaWidth, transitionAreaHeight,
+ layout.width(), layout.height());
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ /**
* Create a fullscreen indicator with no animation
*/
private void createView() {
@@ -85,11 +151,30 @@
final DisplayMetrics metrics = resources.getDisplayMetrics();
final int screenWidth = metrics.widthPixels;
final int screenHeight = metrics.heightPixels;
+
mView = new View(mContext);
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
+ String description;
+ switch (mType) {
+ case TO_DESKTOP_INDICATOR:
+ description = "Desktop indicator";
+ break;
+ case TO_FULLSCREEN_INDICATOR:
+ description = "Fullscreen indicator";
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ description = "Split Left indicator";
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ description = "Split Right indicator";
+ break;
+ default:
+ description = "Invalid indicator";
+ break;
+ }
mLeash = builder
- .setName("Fullscreen Indicator")
+ .setName(description)
.setContainerLayer()
.build();
t.show(mLeash);
@@ -97,14 +182,14 @@
new WindowManager.LayoutParams(screenWidth, screenHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle("Fullscreen indicator for Task=" + mTaskInfo.taskId);
+ lp.setTitle(description + " for Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
final WindowlessWindowManager windowManager = new WindowlessWindowManager(
mTaskInfo.configuration, mLeash,
null /* hostInputToken */);
mViewHost = new SurfaceControlViewHost(mContext,
mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
- "FullscreenVisualIndicator");
+ "DesktopModeVisualIndicator");
mViewHost.setView(mView, lp);
// We want this indicator to be behind the dragged task, but in front of all others.
t.setRelativeLayer(mLeash, mTaskSurface, -1);
@@ -116,24 +201,13 @@
}
/**
- * Create fullscreen indicator and fades it in.
+ * Create an indicator. Animator fades it in while expanding the bounds outwards.
*/
- public void createFullscreenIndicator() {
- mIsFullscreen = true;
- mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
- animator.start();
- }
-
- /**
- * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards.
- */
- public void createFullscreenIndicatorWithAnimatedBounds() {
- mIsFullscreen = true;
+ public void createIndicatorWithAnimatedBounds() {
+ mIsFullscreen = mType == TO_FULLSCREEN_INDICATOR;
mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .toFullscreenAnimatorWithAnimatedBounds(mView,
+ .animateBounds(mView, mType,
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
}
@@ -143,6 +217,7 @@
*/
public void transitionFullscreenIndicatorToFreeform() {
mIsFullscreen = false;
+ mType = TO_DESKTOP_INDICATOR;
final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
@@ -153,6 +228,7 @@
*/
public void transitionFreeformIndicatorToFullscreen() {
mIsFullscreen = true;
+ mType = TO_FULLSCREEN_INDICATOR;
final VisualIndicatorAnimator animator =
VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds(
mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId));
@@ -160,6 +236,14 @@
}
/**
+ * Determine if a MotionEvent is in the same range that enabled the indicator.
+ * Used to dismiss the indicator when a transition will no longer result from releasing.
+ */
+ public boolean eventOutsideRange(float x, float y) {
+ return !mIndicatorRange.contains((int) x, (int) y);
+ }
+
+ /**
* Release the indicator and its components when it is no longer needed.
*/
public void releaseVisualIndicator(SurfaceControl.Transaction t) {
@@ -210,23 +294,6 @@
* @param view the view for this indicator
* @param displayLayout information about the display the transitioning task is currently on
*/
- public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view,
- @NonNull DisplayLayout displayLayout) {
- final Rect bounds = getMaxBounds(displayLayout);
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, bounds, bounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator);
- return animator;
- }
-
-
- /**
- * Create animator for visual indicator of fullscreen transition
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is currently on
- */
public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds(
@NonNull View view, @NonNull DisplayLayout displayLayout) {
final int padding = displayLayout.stableInsets().top;
@@ -235,7 +302,37 @@
view.getBackground().setBounds(startBounds);
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, getMaxBounds(displayLayout));
+ view, startBounds, getMaxBounds(startBounds));
+ animator.setInterpolator(new DecelerateInterpolator());
+ setupIndicatorAnimation(animator);
+ return animator;
+ }
+
+ public static VisualIndicatorAnimator animateBounds(
+ @NonNull View view, int type, @NonNull DisplayLayout displayLayout) {
+ final int padding = displayLayout.stableInsets().top;
+ Rect startBounds = new Rect();
+ switch (type) {
+ case TO_FULLSCREEN_INDICATOR:
+ startBounds.set(padding, padding,
+ displayLayout.width() - padding,
+ displayLayout.height() - padding);
+ break;
+ case TO_SPLIT_LEFT_INDICATOR:
+ startBounds.set(padding, padding,
+ displayLayout.width() / 2 - padding,
+ displayLayout.height() - padding);
+ break;
+ case TO_SPLIT_RIGHT_INDICATOR:
+ startBounds.set(displayLayout.width() / 2 + padding, padding,
+ displayLayout.width() - padding,
+ displayLayout.height() - padding);
+ break;
+ }
+ view.getBackground().setBounds(startBounds);
+
+ final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
+ view, startBounds, getMaxBounds(startBounds));
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
@@ -252,12 +349,13 @@
final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE;
final int width = displayLayout.width();
final int height = displayLayout.height();
+ Rect startBounds = new Rect(0, 0, width, height);
Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2),
(int) (adjustmentPercentage * height / 2),
(int) (displayLayout.width() - (adjustmentPercentage * width / 2)),
(int) (displayLayout.height() - (adjustmentPercentage * height / 2)));
final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, getMaxBounds(displayLayout), endBounds);
+ view, startBounds, endBounds);
animator.setInterpolator(new DecelerateInterpolator());
setupIndicatorAnimation(animator);
return animator;
@@ -310,21 +408,17 @@
}
/**
- * Return the max bounds of a fullscreen indicator
+ * Return the max bounds of a visual indicator
*/
- private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) {
- final int padding = displayLayout.stableInsets().top;
- final int width = displayLayout.width() - 2 * padding;
- final int height = displayLayout.height() - 2 * padding;
- Rect endBounds = new Rect((int) (padding
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (padding
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)),
- (int) (displayLayout.width() - padding
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)),
- (int) (displayLayout.height() - padding
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)));
- return endBounds;
+ private static Rect getMaxBounds(Rect startBounds) {
+ return new Rect((int) (startBounds.left
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
+ (int) (startBounds.top
+ - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())),
+ (int) (startBounds.right
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())),
+ (int) (startBounds.bottom
+ + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 4740a9d..b918c83 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -29,6 +29,7 @@
import android.content.Context
import android.content.res.TypedArray
import android.graphics.Point
+import android.graphics.PointF
import android.graphics.Rect
import android.graphics.Region
import android.os.IBinder
@@ -55,7 +56,10 @@
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -108,12 +112,17 @@
com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
)
+ private val transitionAreaWidth
+ get() = context.resources.getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_width
+ )
+
// This is public to avoid cyclic dependency; it is set by SplitScreenController
lateinit var splitScreenController: SplitScreenController
init {
desktopMode = DesktopModeImpl()
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
shellInit.addInitCallback({ onInit() }, this)
}
}
@@ -805,7 +814,8 @@
) {
val wct = WindowContainerTransaction()
addMoveToSplitChanges(wct, taskInfo)
- splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds)
}
}
@@ -829,25 +839,36 @@
/**
* Perform checks required on drag move. Create/release fullscreen indicator as needed.
+ * Different sources for x and y coordinates are used due to different needs for each:
+ * We want split transitions to be based on input coordinates but fullscreen transition
+ * to be based on task edge coordinate.
*
* @param taskInfo the task being dragged.
* @param taskSurface SurfaceControl of dragged task.
- * @param y coordinate of dragged task. Used for checks against status bar height.
+ * @param inputCoordinate coordinates of input. Used for checks against left/right edge of screen.
+ * @param taskBounds bounds of dragged task. Used for checks against status bar height.
*/
fun onDragPositioningMove(
- taskInfo: RunningTaskInfo,
- taskSurface: SurfaceControl,
- y: Float
+ taskInfo: RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ inputCoordinate: PointF,
+ taskBounds: Rect
) {
- if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
- if (y <= transitionAreaHeight && visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
- displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicatorWithAnimatedBounds()
- } else if (y > transitionAreaHeight && visualIndicator != null) {
- releaseVisualIndicator()
- }
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
+ var type = DesktopModeVisualIndicator.determineIndicatorType(inputCoordinate,
+ taskBounds, displayLayout, context)
+ if (type != DesktopModeVisualIndicator.INVALID_INDICATOR && visualIndicator == null) {
+ visualIndicator = DesktopModeVisualIndicator(
+ syncQueue, taskInfo,
+ displayController, context, taskSurface, shellTaskOrganizer,
+ rootTaskDisplayAreaOrganizer, type)
+ visualIndicator?.createIndicatorWithAnimatedBounds()
+ return
+ }
+ if (visualIndicator?.eventOutsideRange(inputCoordinate.x,
+ taskBounds.top.toFloat()) == true) {
+ releaseVisualIndicator()
}
}
@@ -856,19 +877,39 @@
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
- * @param y the Y position of the top edge of the task
+ * @param inputCoordinate the coordinates of the motion event
+ * @param taskBounds the updated bounds of the task being dragged.
* @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
- taskInfo: RunningTaskInfo,
- position: Point,
- y: Float,
- windowDecor: DesktopModeWindowDecoration
+ taskInfo: RunningTaskInfo,
+ position: Point,
+ inputCoordinate: PointF,
+ taskBounds: Rect,
+ windowDecor: DesktopModeWindowDecoration
) {
- if (y <= transitionAreaHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
+ return
+ }
+ if (taskBounds.top <= transitionAreaHeight) {
windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
+ if (inputCoordinate.x <= transitionAreaWidth) {
+ releaseVisualIndicator()
+ var wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_TOP_OR_LEFT, taskBounds)
+ }
+ if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
+ ?.minus(transitionAreaWidth) ?: return)) {
+ releaseVisualIndicator()
+ var wct = WindowContainerTransaction()
+ addMoveToSplitChanges(wct, taskInfo)
+ splitScreenController.requestEnterSplitSelect(taskInfo, wct,
+ SPLIT_POSITION_BOTTOM_OR_RIGHT, taskBounds)
+ }
}
/**
@@ -892,8 +933,8 @@
if (visualIndicator == null) {
visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo,
displayController, context, taskSurface, shellTaskOrganizer,
- rootTaskDisplayAreaOrganizer)
- visualIndicator?.createFullscreenIndicator()
+ rootTaskDisplayAreaOrganizer, TO_DESKTOP_INDICATOR)
+ visualIndicator?.createIndicatorWithAnimatedBounds()
}
val indicator = visualIndicator ?: return
if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 22541bbd..a80241e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -68,7 +68,7 @@
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mShellTaskOrganizer.addFocusListener(this);
}
}
@@ -90,7 +90,7 @@
t.apply();
}
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
if (taskInfo.isVisible) {
@@ -111,7 +111,7 @@
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
@@ -135,7 +135,7 @@
taskInfo.taskId);
mWindowDecorationViewModel.onTaskInfoChanged(taskInfo);
state.mTaskInfo = taskInfo;
- if (DesktopModeStatus.isAnyEnabled()) {
+ if (DesktopModeStatus.isEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
@@ -154,7 +154,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
- if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+ if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index e3922d6..018d674 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1059,7 +1059,21 @@
private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
@NonNull SurfaceControl.Transaction startTransaction) {
final SurfaceControl leash = prevPipTaskChange.getLeash();
- startTransaction.remove(leash);
+ final Rect bounds = prevPipTaskChange.getEndAbsBounds();
+ final Point offset = prevPipTaskChange.getEndRelOffset();
+ bounds.offset(-offset.x, -offset.y);
+
+ startTransaction.setWindowCrop(leash, null);
+ startTransaction.setMatrix(leash, 1, 0, 0, 1);
+ startTransaction.setCornerRadius(leash, 0);
+ startTransaction.setPosition(leash, bounds.left, bounds.top);
+
+ if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
+ if (mPipAnimationController.getCurrentAnimator() != null) {
+ mPipAnimationController.getCurrentAnimator().cancel();
+ }
+ startTransaction.setAlpha(leash, 1);
+ }
mHasFadeOut = false;
mCurrentPipTaskToken = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
index ec09827..6dabb3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/OWNERS
@@ -1,3 +1,4 @@
# WM shell sub-module pip owner
hwwang@google.com
mateuszc@google.com
+gabiyev@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index ac142e9..94e1b33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -340,7 +340,7 @@
continue;
}
- if (DesktopModeStatus.isProto2Enabled() && mDesktopModeTaskRepository.isPresent()
+ if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent()
&& mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) {
// Freeform tasks will be added as a separate entry
freeformTasks.add(taskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
index 7171da5..a25f391 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -17,7 +17,7 @@
package com.android.wm.shell.splitscreen;
import android.app.ActivityManager.RunningTaskInfo;
-
+import android.graphics.Rect;
/**
* Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
*/
@@ -25,5 +25,5 @@
/**
* Called when a task requests to enter split select
*/
- boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+ boolean onRequestSplitSelect(in RunningTaskInfo taskInfo, int splitPosition, in Rect taskBounds);
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index f20fe0b..ad40493 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -66,7 +66,8 @@
/** Callback interface for listening to requests to enter split select */
interface SplitSelectListener {
- default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+ default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+ int splitPosition, Rect taskBounds) {
return false;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 210bf68..f90ee58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -507,10 +507,12 @@
* Move a task to split select
* @param taskInfo the task being moved to split select
* @param wct transaction to apply if this is a valid request
+ * @param splitPosition the split position this task should move to
+ * @param taskBounds current freeform bounds of the task entering split
*/
public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
- WindowContainerTransaction wct) {
- mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+ WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
+ mStageCoordinator.requestEnterSplitSelect(taskInfo, wct, splitPosition, taskBounds);
}
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
@@ -1135,9 +1137,11 @@
new SplitScreen.SplitSelectListener() {
@Override
public boolean onRequestEnterSplitSelect(
- ActivityManager.RunningTaskInfo taskInfo) {
+ ActivityManager.RunningTaskInfo taskInfo, int splitPosition,
+ Rect taskBounds) {
AtomicBoolean result = new AtomicBoolean(false);
- mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+ mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo,
+ splitPosition, taskBounds)));
return result.get();
}
};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 6970068..842b1bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -466,10 +466,11 @@
}
void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
- WindowContainerTransaction wct) {
+ WindowContainerTransaction wct, int splitPosition, Rect taskBounds) {
boolean enteredSplitSelect = false;
for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
- enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+ enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo, splitPosition,
+ taskBounds);
}
if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 986560b..87ceaa4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -43,7 +43,6 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.split.SplitScreenUtils;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
@@ -71,7 +70,6 @@
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
private final KeyguardTransitionHandler mKeyguardHandler;
- private DesktopModeController mDesktopModeController;
private DesktopTasksController mDesktopTasksController;
private UnfoldTransitionHandler mUnfoldHandler;
@@ -141,7 +139,6 @@
@Nullable PipTransitionController pipTransitionController,
Optional<RecentsTransitionHandler> recentsHandlerOptional,
KeyguardTransitionHandler keyguardHandler,
- Optional<DesktopModeController> desktopModeControllerOptional,
Optional<DesktopTasksController> desktopTasksControllerOptional,
Optional<UnfoldTransitionHandler> unfoldHandler) {
mPlayer = player;
@@ -161,7 +158,6 @@
if (mRecentsHandler != null) {
mRecentsHandler.addMixer(this);
}
- mDesktopModeController = desktopModeControllerOptional.orElse(null);
mDesktopTasksController = desktopTasksControllerOptional.orElse(null);
mUnfoldHandler = unfoldHandler.orElse(null);
}, this);
@@ -244,7 +240,7 @@
@Override
public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
- || DesktopModeStatus.isActive(mPlayer.getContext()))) {
+ || DesktopModeStatus.isEnabled())) {
return this;
}
return null;
@@ -259,7 +255,7 @@
MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition);
mixed.mLeftoversHandler = mRecentsHandler;
mActiveTransitions.add(mixed);
- } else if (DesktopModeStatus.isActive(mPlayer.getContext())) {
+ } else if (DesktopModeStatus.isEnabled()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while "
+ "desktop mode is active, so treat it as Mixed.");
final MixedTransition mixed = new MixedTransition(
@@ -666,11 +662,6 @@
if (!consumed) {
return false;
}
- //Sync desktop mode state (proto 1)
- if (mDesktopModeController != null) {
- mDesktopModeController.syncSurfaceState(info, finishTransaction);
- return true;
- }
//Sync desktop mode state (proto 2)
if (mDesktopTasksController != null) {
mDesktopTasksController.syncSurfaceState(info, finishTransaction);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 026e973..abd2ad4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -36,6 +36,7 @@
import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
@@ -68,7 +69,6 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -101,7 +101,6 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopModeController> mDesktopModeController;
private final Optional<DesktopTasksController> mDesktopTasksController;
private boolean mTransitionDragActive;
@@ -134,7 +133,6 @@
ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
) {
@@ -148,7 +146,6 @@
shellController,
syncQueue,
transitions,
- desktopModeController,
desktopTasksController,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
@@ -168,7 +165,6 @@
ShellController shellController,
SyncTransactionQueue syncQueue,
Transitions transitions,
- Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
@@ -184,7 +180,6 @@
mDisplayController = displayController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -213,9 +208,8 @@
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
if (visible) {
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.isActive(mContext)
+ if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
}
}
@@ -375,7 +369,6 @@
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
if (mDesktopTasksController.isPresent()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
// App sometimes draws before the insets from WindowDecoration#relayout have
@@ -386,7 +379,6 @@
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
decoration.closeHandleMenu();
} else if (id == R.id.split_screen_button) {
@@ -464,7 +456,6 @@
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
- mDesktopModeController.ifPresent(c -> c.moveTaskToFront(taskInfo));
}
}
@@ -475,15 +466,10 @@
@Override
public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (DesktopModeStatus.isProto2Enabled()
+ if (DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return false;
}
- if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
- == WINDOWING_MODE_FULLSCREEN) {
- return false;
- }
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
@@ -507,7 +493,9 @@
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
- decoration.mTaskSurface, newTaskBounds.top));
+ decoration.mTaskSurface,
+ new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ newTaskBounds));
mIsDragging = true;
mShouldClick = false;
return true;
@@ -536,7 +524,9 @@
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId)));
+ position,
+ new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
+ newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
mIsDragging = false;
return true;
}
@@ -629,7 +619,7 @@
*/
private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
if (relevantDecor == null
|| relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
|| mTransitionDragActive) {
@@ -638,14 +628,10 @@
}
handleEventOutsideFocusedCaption(ev, relevantDecor);
// Prevent status bar from reacting to a caption drag.
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
if (mTransitionDragActive) {
inputMonitor.pilferPointers();
}
- } else if (DesktopModeStatus.isProto1Enabled()) {
- if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- inputMonitor.pilferPointers();
- }
}
}
@@ -678,7 +664,7 @@
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
@@ -703,10 +689,8 @@
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > 2 * statusBarHeight) {
- if (DesktopModeStatus.isProto2Enabled()) {
+ if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
- } else if (DesktopModeStatus.isProto1Enabled()) {
- mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
}
mMoveToDesktopAnimator = null;
return;
@@ -897,7 +881,7 @@
&& taskInfo.isFocused) {
return false;
}
- return DesktopModeStatus.isProto2Enabled()
+ return DesktopModeStatus.isEnabled()
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
@@ -979,13 +963,11 @@
@Override
public void onTaskCornersChanged(int taskId, Region corner) {
- mDesktopModeController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
}
@Override
public void onTaskCornersRemoved(int taskId) {
- mDesktopModeController.ifPresent(d -> d.removeCornersForTask(taskId));
mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a75dce2..3e21c8c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -428,7 +428,7 @@
.setOnTouchListener(mOnCaptionTouchListener)
.setLayoutId(mRelayoutParams.mLayoutResId)
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
- .setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
+ .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
.build();
mHandleMenu.show();
}
@@ -549,9 +549,6 @@
}
private int getDesktopModeWindowDecorLayoutId(int windowingMode) {
- if (DesktopModeStatus.isProto1Enabled()) {
- return R.layout.desktop_mode_app_controls_window_decor;
- }
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index ac4a597..ca7cbfd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -236,7 +236,7 @@
t.setPosition(mAppInfoPill.mWindowSurface,
mAppInfoPillPosition.x, mAppInfoPillPosition.y);
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
- final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
+ final boolean shouldShowWindowingPill = DesktopModeStatus.isEnabled();
if (shouldShowWindowingPill) {
t.setPosition(mWindowingPill.mWindowSurface,
mWindowingPillPosition.x, mWindowingPillPosition.y);
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index dfbadae..434b008 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -69,10 +69,22 @@
}
filegroup {
+ name: "WMShellFlickerServicePlatinumTests-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+ "src/com/android/wm/shell/flicker/service/*/scenarios/**/*.kt",
+ "src/com/android/wm/shell/flicker/service/common/**/*.kt",
+ ],
+}
+
+filegroup {
name: "WMShellFlickerServiceTests-src",
srcs: [
"src/com/android/wm/shell/flicker/service/**/*.kt",
],
+ exclude_srcs: [
+ "src/com/android/wm/shell/flicker/service/*/platinum/**/*.kt",
+ ],
}
java_library {
@@ -143,6 +155,7 @@
":WMShellFlickerTestsSplitScreenGroup2-src",
":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerServiceTests-src",
+ ":WMShellFlickerServicePlatinumTests-src",
],
}
@@ -210,3 +223,15 @@
":WMShellFlickerServiceTests-src",
],
}
+
+android_test {
+ name: "WMShellFlickerServicePlatinumTests",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestService.xml"],
+ package_name: "com.android.wm.shell.flicker.service",
+ instrumentation_target_package: "com.android.wm.shell.flicker.service",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerServicePlatinumTests-src",
+ ],
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
index fa723e3..5f15785 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/Utils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/common/Utils.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.flicker.service
+package com.android.wm.shell.flicker.service.common
import android.app.Instrumentation
import android.platform.test.rule.NavigationModeRule
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
new file mode 100644
index 0000000..a5c5122
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavLandscape.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
new file mode 100644
index 0000000..092fb67
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/CopyContentInSplitGesturalNavPortrait.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+ @ExpectedScenarios([]) @Test override fun copyContentInSplit() = super.copyContentInSplit()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..8cb25fe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavLandscape :
+ DismissSplitScreenByDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..fa1be63
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByDividerGesturalNavPortrait :
+ DismissSplitScreenByDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByDivider() = super.dismissSplitScreenByDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..aa35237
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavLandscape :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..e195360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DismissSplitScreenByGoHomeGesturalNavPortrait :
+ DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_EXIT"])
+ @Test
+ override fun dismissSplitScreenByGoHome() = super.dismissSplitScreenByGoHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
new file mode 100644
index 0000000..c1b3aad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
new file mode 100644
index 0000000..c6e2e85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/DragDividerToResizeGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_RESIZE"])
+ @Test
+ override fun dragDividerToResize() = super.dragDividerToResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
new file mode 100644
index 0000000..5f771c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
new file mode 100644
index 0000000..729a401
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+ EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromAllApps() = super.enterSplitScreenByDragFromAllApps()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
new file mode 100644
index 0000000..6e4cf9f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
new file mode 100644
index 0000000..cc28702
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+ EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromNotification() =
+ super.enterSplitScreenByDragFromNotification()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
new file mode 100644
index 0000000..736604f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
new file mode 100644
index 0000000..8df8dfa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+ EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromShortcut() = super.enterSplitScreenByDragFromShortcut()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
new file mode 100644
index 0000000..378f055
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
new file mode 100644
index 0000000..b33d262
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+ EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenByDragFromTaskbar() = super.enterSplitScreenByDragFromTaskbar()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
new file mode 100644
index 0000000..b1d3858
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavLandscape :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
new file mode 100644
index 0000000..6d824c7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class EnterSplitScreenFromOverviewGesturalNavPortrait :
+ EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
+ @Test
+ override fun enterSplitScreenFromOverview() = super.enterSplitScreenFromOverview()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
new file mode 100644
index 0000000..f1d3d0c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
new file mode 100644
index 0000000..a867bac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+ SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios([])
+ @Test
+ override fun switchAppByDoubleTapDivider() = super.switchAppByDoubleTapDivider()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
new file mode 100644
index 0000000..76247ba
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
new file mode 100644
index 0000000..e179da8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+ SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromAnotherApp() = super.switchBackToSplitFromAnotherApp()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
new file mode 100644
index 0000000..20f554f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavLandscape :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
new file mode 100644
index 0000000..f7776ee
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromHomeGesturalNavPortrait :
+ SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromHome() = super.switchBackToSplitFromHome()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
new file mode 100644
index 0000000..00f6073
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavLandscape :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
new file mode 100644
index 0000000..b3340e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBackToSplitFromRecentGesturalNavPortrait :
+ SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
new file mode 100644
index 0000000..3da61e5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
new file mode 100644
index 0000000..627ae18
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.Rotation
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun switchBetweenSplitPairs() = super.switchBetweenSplitPairs()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
new file mode 100644
index 0000000..f4e7298
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
new file mode 100644
index 0000000..f38b2e8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.service.splitscreen.flicker
+
+import android.tools.common.flicker.FlickerConfig
+import android.tools.common.flicker.annotation.ExpectedScenarios
+import android.tools.common.flicker.annotation.FlickerConfigProvider
+import android.tools.common.flicker.config.FlickerConfig
+import android.tools.common.flicker.config.FlickerServiceConfig
+import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.splitscreen.scenarios.UnlockKeyguardToSplitScreen
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+
+ @ExpectedScenarios(["QUICKSWITCH"])
+ @Test
+ override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index e530f63..245184c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index e9fc437..1f2f1ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index 416692c..ebbf7c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 494a246..71e701c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
index 9b43816..c433b21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
index 50151f1..3f087a5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
index 76fbf60..767e7b5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
index f8e43f1..2592fd4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Assume
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index c2100f6..f2cbf24 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 70f3bed..538ed96 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -25,7 +25,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 86f394d..0dab5ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index d7b611e..ad3a2d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index 3cc5df0..b780a16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 4a9c32f..329d61d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
index 383a6b3..a9933bbe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt
@@ -23,7 +23,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.wm.shell.flicker.service.Utils
+import com.android.wm.shell.flicker.service.common.Utils
import com.android.wm.shell.flicker.utils.SplitScreenUtils
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
new file mode 100644
index 0000000..9655f97
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.IWindowContainerToken;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TransitionInfoBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests of {@link BubblesTransitionObserver}.
+ */
+@SmallTest
+public class BubblesTransitionObserverTest {
+
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private BubbleData mBubbleData;
+
+ @Mock
+ private IBinder mTransition;
+ @Mock
+ private SurfaceControl.Transaction mStartT;
+ @Mock
+ private SurfaceControl.Transaction mFinishT;
+
+ @Mock
+ private Bubble mBubble;
+
+ private BubblesTransitionObserver mTransitionObserver;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData);
+ }
+
+ @Test
+ public void testOnTransitionReady_open_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_toFront_collapsesStack() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskInfo_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Null task info
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */);
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noTaskId_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Invalid task id
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT,
+ createTaskInfo(INVALID_TASK_ID));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_notOpening_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // Transits that aren't opening
+ TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4));
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackAnimating_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_stackNotExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_noSelectedBubble_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ @Test
+ public void testOnTransitionReady_openingMatchesExpanded_skip() {
+ when(mBubbleData.isExpanded()).thenReturn(true);
+ when(mBubbleData.getSelectedBubble()).thenReturn(mBubble);
+ when(mBubble.getTaskId()).thenReturn(1);
+ when(mBubbleController.isStackAnimating()).thenReturn(false);
+
+ // What's moving to front is same as the opened bubble
+ TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1));
+
+ mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT);
+
+ verify(mBubbleData, never()).setExpanded(eq(false));
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) {
+ final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ return taskInfo;
+ }
+
+ private TransitionInfo createTransitionInfo(int changeType,
+ ActivityManager.RunningTaskInfo info) {
+ final TransitionInfo.Change change = new TransitionInfo.Change(
+ new WindowContainerToken(mock(IWindowContainerToken.class)),
+ mock(SurfaceControl.class));
+ change.setMode(changeType);
+ change.setTaskInfo(info);
+
+ return new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(change).build();
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
deleted file mode 100644
index 605a762..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
- * Copyright (C) 2022 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.wm.shell.desktopmode;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createFullscreenTask;
-import static com.android.wm.shell.desktopmode.DesktopTestHelpers.createHomeTask;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.testing.AndroidTestingRunner;
-import android.window.DisplayAreaInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
-import android.window.WindowContainerTransaction.HierarchyOp;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.wm.shell.MockToken;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.sysui.ShellController;
-import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DesktopModeControllerTest extends ShellTestCase {
-
- private static final int SECOND_DISPLAY = 2;
-
- @Mock
- private ShellController mShellController;
- @Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
- private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
- @Mock
- private ShellExecutor mTestExecutor;
- @Mock
- private Handler mMockHandler;
- @Mock
- private Transitions mTransitions;
- private DesktopModeController mController;
- private DesktopModeTaskRepository mDesktopModeTaskRepository;
- private ShellInit mShellInit;
- private StaticMockitoSession mMockitoSession;
-
- @Before
- public void setUp() {
- mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
- when(DesktopModeStatus.isActive(any())).thenReturn(true);
-
- mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
-
- mDesktopModeTaskRepository = new DesktopModeTaskRepository();
-
- mController = createController();
-
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
-
- mShellInit.init();
- clearInvocations(mShellTaskOrganizer);
- clearInvocations(mRootTaskDisplayAreaOrganizer);
- clearInvocations(mTransitions);
- }
-
- @After
- public void tearDown() {
- mMockitoSession.finishMocking();
- }
-
- @Test
- public void instantiate_addInitCallback() {
- verify(mShellInit).addInitCallback(any(), any());
- }
-
- @Test
- public void instantiate_flagOff_doNotAddInitCallback() {
- when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
- clearInvocations(mShellInit);
-
- createController();
-
- verify(mShellInit, never()).addInitCallback(any(), any());
- }
-
- @Test
- public void testDesktopModeEnabled_rootTdaSetToFreeform() {
- DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 1 change: Root TDA windowing mode
- assertThat(wct.getChanges().size()).isEqualTo(1);
- // Verify WCT has a change for setting windowing mode to freeform
- Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
- }
-
- @Test
- public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
- DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
-
- mController.updateDesktopModeActive(false);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 1 change: Root TDA windowing mode
- assertThat(wct.getChanges().size()).isEqualTo(1);
- // Verify WCT has a change for setting windowing mode to fullscreen
- Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
- }
-
- @Test
- public void testDesktopModeEnabled_windowingModeCleared() {
- createMockDisplayArea();
- RunningTaskInfo freeformTask = createFreeformTask();
- RunningTaskInfo fullscreenTask = createFullscreenTask();
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 2 changes: Root TDA windowing mode and 1 task
- assertThat(wct.getChanges().size()).isEqualTo(2);
- // No changes for tasks that are not standard or freeform
- assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
- assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
- // Standard freeform task has windowing mode cleared
- Change change = wct.getChanges().get(freeformTask.token.asBinder());
- assertThat(change).isNotNull();
- assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
- createMockDisplayArea();
- RunningTaskInfo freeformTask = createFreeformTask();
- RunningTaskInfo fullscreenTask = createFullscreenTask();
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(freeformTask, fullscreenTask, homeTask)));
-
- mController.updateDesktopModeActive(false);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // 3 changes: Root TDA windowing mode and 2 tasks
- assertThat(wct.getChanges().size()).isEqualTo(3);
- // No changes to home task
- assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
- // Standard tasks have bounds cleared
- assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
- assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
- // Freeform standard tasks have windowing mode cleared
- assertThat(wct.getChanges().get(
- freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
- WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
- createMockDisplayArea();
- RunningTaskInfo fullscreenTask1 = createFullscreenTask();
- fullscreenTask1.isVisible = true;
- RunningTaskInfo fullscreenTask2 = createFullscreenTask();
- fullscreenTask2.isVisible = false;
- RunningTaskInfo homeTask = createHomeTask();
- when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
- Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
-
- mController.updateDesktopModeActive(true);
- WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
-
- // Check that there are hierarchy changes for home task and visible task
- assertThat(wct.getHierarchyOps()).hasSize(2);
- // First show home task
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
-
- // Then visible task on top of it
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
- // Set up two active tasks on desktop, task2 is on top of task1.
- RunningTaskInfo freeformTask1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, freeformTask1.taskId, false /* visible */);
- RunningTaskInfo freeformTask2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, freeformTask2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, freeformTask2.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
- freeformTask1);
- when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
- freeformTask2);
-
- // Run show desktop apps logic
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
-
- // Task 1 appeared first, must be first reorder to top.
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
-
- // Task 2 appeared last, must be last reorder to top.
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_appsAlreadyVisible_bringsToFront() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Check wct has reorder calls
- assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 1 appeared first, must be first reorder to top.
- HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
-
- // Task 2 appeared last, must be last reorder to top.
- HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_someAppsInvisible_reordersAll() {
- final RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
- final RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- // Both tasks should be reordered to top, even if one was already visible.
- assertThat(wct.getHierarchyOps()).hasSize(2);
- final HierarchyOp op1 = wct.getHierarchyOps().get(0);
- assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
- final HierarchyOp op2 = wct.getHierarchyOps().get(1);
- assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
- }
-
- @Test
- public void testShowDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask(DEFAULT_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- DEFAULT_DISPLAY, taskDefaultDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskDefaultDisplay.taskId)).thenReturn(
- taskDefaultDisplay);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask(SECOND_DISPLAY);
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(
- SECOND_DISPLAY, taskSecondDisplay.taskId, false /* visible */);
- when(mShellTaskOrganizer.getRunningTaskInfo(taskSecondDisplay.taskId)).thenReturn(
- taskSecondDisplay);
-
- mController.showDesktopApps(DEFAULT_DISPLAY);
-
- WindowContainerTransaction wct = getBringAppsToFrontTransaction();
- assertThat(wct.getHierarchyOps()).hasSize(1);
- HierarchyOp op = wct.getHierarchyOps().get(0);
- assertThat(op.getContainer()).isEqualTo(taskDefaultDisplay.token.asBinder());
- }
-
- @Test
- public void testGetVisibleTaskCount_noTasks_returnsZero() {
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_bothVisible_returnsTwo() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasks_oneVisible_returnsOne() {
- RunningTaskInfo task1 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task1.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task1.taskId,
- true /* visible */);
-
- RunningTaskInfo task2 = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, task2.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task2.taskId,
- false /* visible */);
-
- assertThat(mController.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1);
- }
-
- @Test
- public void testGetVisibleTaskCount_twoTasksVisibleOnDifferentDisplays_returnsOne() {
- RunningTaskInfo taskDefaultDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(DEFAULT_DISPLAY, taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskDefaultDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY,
- taskDefaultDisplay.taskId,
- true /* visible */);
-
- RunningTaskInfo taskSecondDisplay = createFreeformTask();
- mDesktopModeTaskRepository.addActiveTask(SECOND_DISPLAY, taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(taskSecondDisplay.taskId);
- mDesktopModeTaskRepository.updateVisibleFreeformTasks(SECOND_DISPLAY,
- taskSecondDisplay.taskId,
- true /* visible */);
-
- assertThat(mController.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1);
- }
-
- @Test
- public void testHandleTransitionRequest_desktopModeNotActive_returnsNull() {
- when(DesktopModeStatus.isActive(any())).thenReturn(false);
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_unsupportedTransit_returnsNull() {
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_notFreeform_returnsNull() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- WindowContainerTransaction wct = mController.handleRequest(
- new Binder(),
- new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
- assertThat(wct).isNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskOpen_returnsWct() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- WindowContainerTransaction wct = mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
- assertThat(wct).isNotNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskToFront_returnsWct() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- WindowContainerTransaction wct = mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_TO_FRONT, trigger, null /* remote */));
- assertThat(wct).isNotNull();
- }
-
- @Test
- public void testHandleTransitionRequest_taskOpen_doesNotStartAnotherTransition() {
- RunningTaskInfo trigger = new RunningTaskInfo();
- trigger.token = new MockToken().token();
- trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- mController.handleRequest(
- mock(IBinder.class),
- new TransitionRequestInfo(TRANSIT_OPEN, trigger, null /* remote */));
- verifyZeroInteractions(mTransitions);
- }
-
- private DesktopModeController createController() {
- return new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
- mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- }
-
- private DisplayAreaInfo createMockDisplayArea() {
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().token(),
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
- return displayAreaInfo;
- }
-
- private WindowContainerTransaction getDesktopModeSwitchTransaction() {
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
- } else {
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
- }
- return arg.getValue();
- }
-
- private WindowContainerTransaction getBringAppsToFrontTransaction() {
- final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- verify(mTransitions).startTransition(eq(TRANSIT_NONE), arg.capture(), any());
- } else {
- verify(mShellTaskOrganizer).applyTransaction(arg.capture());
- }
- return arg.getValue();
- }
-
- private void assertThatBoundsCleared(Change change) {
- assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
- assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
- }
-
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 5d87cf8..be4a287 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -107,7 +107,7 @@
@Before
fun setUp() {
mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
shellInit = Mockito.spy(ShellInit(testExecutor))
desktopModeTaskRepository = DesktopModeTaskRepository()
@@ -154,7 +154,7 @@
@Test
fun instantiate_flagOff_doNotAddInitCallback() {
- whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false)
+ whenever(DesktopModeStatus.isEnabled()).thenReturn(false)
clearInvocations(shellInit)
createController()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 9e9e1ca..40ce785 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -257,7 +257,7 @@
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(true);
+ when(DesktopModeStatus.isEnabled()).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
@@ -297,7 +297,7 @@
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isProto2Enabled()).thenReturn(false);
+ when(DesktopModeStatus.isEnabled()).thenReturn(false);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 7f0465a..d8afe68 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -55,7 +55,6 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -88,7 +87,6 @@
@Mock private DisplayController mDisplayController;
@Mock private DisplayLayout mDisplayLayout;
@Mock private SyncTransactionQueue mSyncQueue;
- @Mock private DesktopModeController mDesktopModeController;
@Mock private DesktopTasksController mDesktopTasksController;
@Mock private InputMonitor mInputMonitor;
@Mock private InputManager mInputManager;
@@ -121,7 +119,6 @@
mShellController,
mSyncQueue,
mTransitions,
- Optional.of(mDesktopModeController),
Optional.of(mDesktopTasksController),
mDesktopModeWindowDecorFactory,
mMockInputMonitorFactory,
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5ffec34..6523469 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -1013,13 +1013,13 @@
const auto& assets = GetApkAssets(step.cookie);
log_stream << "\n\t" << prefix << ": " << (assets ? assets->GetDebugName() : "<null>")
<< " #" << step.cookie;
- if (!step.config_name.isEmpty()) {
+ if (!step.config_name.empty()) {
log_stream << " - " << step.config_name;
}
}
log_stream << "\nBest matching is from "
- << (last_resolution_.best_config_name.isEmpty() ? "default"
+ << (last_resolution_.best_config_name.empty() ? "default"
: last_resolution_.best_config_name)
<< " configuration of " << last_resolution_.best_package_name;
return log_stream.str();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 120d812..d54c5f5 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -162,7 +162,6 @@
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
- mHintSessionWrapper.destroy();
}
static void setBufferCount(ANativeWindow* window) {
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
index d344a981..858813f 100644
--- a/libs/incident/src/IncidentReportArgs.cpp
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -152,8 +152,8 @@
}
mPrivacyPolicy = privacyPolicy;
- mReceiverPkg = String8(in->readString16()).string();
- mReceiverCls = String8(in->readString16()).string();
+ mReceiverPkg = String8(in->readString16()).c_str();
+ mReceiverCls = String8(in->readString16()).c_str();
int32_t gzip;
err = in->readInt32(&gzip);
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 3716e01..60bb00a 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -196,7 +196,7 @@
vector<uint8_t> dataArg;
dataArg.assign(data, data + size);
Status status = service->addData(tag, dataArg, flags);
- ALOGD("service->add returned %s", status.toString8().string());
+ ALOGD("service->add returned %s", status.toString8().c_str());
return status;
}
@@ -230,7 +230,7 @@
android::base::unique_fd uniqueFd(fd);
android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd));
Status status = service->addFile(tag, parcelFd, flags);
- ALOGD("service->add returned %s", status.toString8().string());
+ ALOGD("service->add returned %s", status.toString8().c_str());
return status;
}
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index b0769ab..e28ad67 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1525,50 +1525,61 @@
/**
* Gets the GNSS measurement's code type.
*
- * <p>Similar to the Attribute field described in RINEX 3.03, e.g., in Tables 4-10, and Table
- * A2 at the RINEX 3.03 Update 1 Document.
+ * <p>Similar to the Attribute field described in RINEX 4.00, e.g., in Tables 9-16 (see
+ * https://igs.org/wg/rinex/#documents-formats).
*
- * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A, IRNSS SA.
+ * <p>Returns "A" for GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS, GLONASS G1a L1OCd,
+ * GLONASS G2a L2CSI.
*
- * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B, IRNSS SB.
+ * <p>Returns "B" for GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D), IRNSS SB RS (D), GLONASS G1a
+ * L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
*
- * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
- * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C.
+ * <p>Returns "C" for GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A, GLONASS G2 C/A, GALILEO E1C,
+ * GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P), IRNSS SC RS (P).
*
- * <p>Returns "D" for BDS B1C D.
+ * <p>Returns "D" for GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)), QZSS L5S(I), BDS B1C Data,
+ * BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+ *
+ * <p>Returns “E” for QZSS L1 C/B, QZSS L6E.
*
* <p>Returns "I" for GPS L5 I, GLONASS G3 I, GALILEO E5a I, GALILEO E5b I, GALILEO E5a+b I,
* SBAS L5 I, QZSS L5 I, BDS B1 I, BDS B2 I, BDS B3 I.
*
- * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), LEX(6) L.
+ * <p>Returns "L" for GPS L1C (P), GPS L2C (L), QZSS L1C (P), QZSS L2C (L), QZSS L6P, BDS
+ * B1a Pilot.
*
* <p>Returns "M" for GPS L1M, GPS L2M.
*
* <p>Returns "N" for GPS L1 codeless, GPS L2 codeless.
*
- * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C P.
+ * <p>Returns "P" for GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P, BDS B1C Pilot, BDS B2a Pilot,
+ * BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot, QZSS L5S(Q).
*
* <p>Returns "Q" for GPS L5 Q, GLONASS G3 Q, GALILEO E5a Q, GALILEO E5b Q, GALILEO E5a+b Q,
* SBAS L5 Q, QZSS L5 Q, BDS B1 Q, BDS B2 Q, BDS B3 Q.
*
- * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), LEX(6) S.
+ * <p>Returns "S" for GPS L1C (D), GPS L2C (M), QZSS L1C (D), QZSS L2C (M), QZSS L6D, BDS B1a
+ * Data.
*
* <p>Returns "W" for GPS L1 Z-tracking, GPS L2 Z-tracking.
*
- * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G3 (I+Q), GALILEO
- * E1 (B+C), GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b(I+Q), GALILEO E6 (B+C), SBAS
- * L5 (I+Q), QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q), LEX(6) (S+L), BDS B1 (I+Q), BDS
- * B1C (D+P), BDS B2 (I+Q), BDS B3 (I+Q), IRNSS L5 (B+C).
+ * <p>Returns "X" for GPS L1C (D+P), GPS L2C (M+L), GPS L5 (I+Q), GLONASS G1a L1OCd+L1OCp,
+ * GLONASS G2a L2CSI+L2OCp, GLONASS G3 (I+Q), GALILEO E1 (B+C), GALILEO E5a (I+Q), GALILEO
+ * E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q), QZSS L1C (D+P), QZSS L2C
+ * (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q), BDS B1C Data+Pilot, BDS B2a Data+Pilot,
+ * BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot, BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
*
* <p>Returns "Y" for GPS L1Y, GPS L2Y.
*
- * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1-SAIF.
+ * <p>Returns "Z" for GALILEO E1 (A+B+C), GALILEO E6 (A+B+C), QZSS L1S/L1-SAIF, QZSS L5S (I+Q),
+ * QZSS L6(D+E), BDS B1A Data+Pilot, BDS B2b Data+Pilot, BDS B3a Data+Pilot.
*
* <p>Returns "UNKNOWN" if the GNSS Measurement's code type is unknown.
*
- * <p>This is used to specify the observation descriptor defined in GNSS Observation Data File
- * Header Section Description in the RINEX standard (Version 3.XX), in cases where the code type
- * does not align with the above listed values. For example, if a code type "G" is added, this
+ * <p>The code type is used to specify the observation descriptor defined in GNSS Observation
+ * Data File Header Section Description in the RINEX standard (Version 4.00). In cases where
+ * the code type does not align with the above listed values, the code type from the most
+ * recent version of RINEX should be used. For example, if a code type "G" is added, this
* string shall be set to "G".
*/
@NonNull
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 0eb657a..fd3e5a2 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -709,11 +709,9 @@
/**
* Returns the Mean Sea Level altitude of this location in meters.
*
- * @throws IllegalStateException if {@link #hasMslAltitude()} is false.
+ * <p>This is only valid if {@link #hasMslAltitude()} is true.
*/
public @FloatRange double getMslAltitudeMeters() {
- Preconditions.checkState(hasMslAltitude(),
- "The Mean Sea Level altitude of this location is not set.");
return mMslAltitudeMeters;
}
@@ -744,11 +742,9 @@
* percentile confidence level. This means that there is 68% chance that the true Mean Sea Level
* altitude of this location falls within {@link #getMslAltitudeMeters()} +/- this uncertainty.
*
- * @throws IllegalStateException if {@link #hasMslAltitudeAccuracy()} is false.
+ * <p>This is only valid if {@link #hasMslAltitudeAccuracy()} is true.
*/
public @FloatRange(from = 0.0) float getMslAltitudeAccuracyMeters() {
- Preconditions.checkState(hasMslAltitudeAccuracy(),
- "The Mean Sea Level altitude accuracy of this location is not set.");
return mMslAltitudeAccuracyMeters;
}
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index da2e56f..382e65d 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -617,7 +617,7 @@
"match the ImageReader's configured buffer format 0x%x.",
bufferFormat, ctx->getBufferFormat());
jniThrowException(env, "java/lang/UnsupportedOperationException",
- msg.string());
+ msg.c_str());
return -1;
}
}
@@ -795,7 +795,7 @@
String8 msg;
msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
" must be 0", halReaderFormat, numPlanes);
- jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
return NULL;
}
@@ -860,7 +860,7 @@
String8 msg;
msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
" must be 0", halReaderFormat, numPlanes);
- jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
return NULL;
}
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 1927b6c..f64233f 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -1068,7 +1068,7 @@
String8 msg;
msg.appendFormat("Format 0x%x is opaque, thus not writable, the number of planes (%d)"
" must be 0", writerFormat, numPlanes);
- jniThrowException(env, "java/lang/IllegalArgumentException", msg.string());
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg.c_str());
return NULL;
}
diff --git a/media/jni/android_media_MediaCrypto.cpp b/media/jni/android_media_MediaCrypto.cpp
index f491be8..5506f61 100644
--- a/media/jni/android_media_MediaCrypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -299,7 +299,7 @@
std::string strerr(StrCryptoError(err));
msg.appendFormat(": general failure (%s)", strerr.c_str());
}
- jniThrowException(env, "android/media/MediaCryptoException", msg.string());
+ jniThrowException(env, "android/media/MediaCryptoException", msg.c_str());
}
}
diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp
index b70818d..c616b84f 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -708,8 +708,8 @@
jclass clazz = gFields.hashmapClassId;
jobject hashMap = env->NewObject(clazz, gFields.hashmap.init);
for (size_t i = 0; i < map.size(); ++i) {
- jstring jkey = env->NewStringUTF(map.keyAt(i).string());
- jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
+ jstring jkey = env->NewStringUTF(map.keyAt(i).c_str());
+ jstring jvalue = env->NewStringUTF(map.valueAt(i).c_str());
env->CallObjectMethod(hashMap, gFields.hashmap.put, jkey, jvalue);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jvalue);
@@ -1169,7 +1169,7 @@
jbyteArray jrequest = VectorToJByteArray(env, request);
env->SetObjectField(keyObj, gFields.keyRequest.data, jrequest);
- jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.c_str());
env->SetObjectField(keyObj, gFields.keyRequest.defaultUrl, jdefaultUrl);
switch (keyRequestType) {
@@ -1332,7 +1332,7 @@
jbyteArray jrequest = VectorToJByteArray(env, request);
env->SetObjectField(provisionObj, gFields.provisionRequest.data, jrequest);
- jstring jdefaultUrl = env->NewStringUTF(defaultUrl.string());
+ jstring jdefaultUrl = env->NewStringUTF(defaultUrl.c_str());
env->SetObjectField(provisionObj, gFields.provisionRequest.defaultUrl, jdefaultUrl);
}
@@ -1686,7 +1686,7 @@
return NULL;
}
- return env->NewStringUTF(value.string());
+ return env->NewStringUTF(value.c_str());
}
static jbyteArray android_media_MediaDrm_getPropertyByteArray(
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index ddc51cd..1458758 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -126,7 +126,7 @@
tmp = NULL;
// Don't let somebody trick us in to reading some random block of memory
- if (strncmp("mem://", pathStr.string(), 6) == 0) {
+ if (strncmp("mem://", pathStr.c_str(), 6) == 0) {
jniThrowException(
env, "java/lang/IllegalArgumentException", "Invalid pathname");
return;
@@ -149,7 +149,7 @@
env,
retriever->setDataSource(
httpService,
- pathStr.string(),
+ pathStr.c_str(),
headersVector.size() > 0 ? &headersVector : NULL),
"java/lang/RuntimeException",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 44aff64..3551ea4 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1234,7 +1234,7 @@
String8 vendorMessage;
if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
vendorMessage = String8::format("DRM vendor-defined error: %d", err);
- drmMessage = vendorMessage.string();
+ drmMessage = vendorMessage.c_str();
}
if (err == BAD_VALUE) {
@@ -1260,7 +1260,7 @@
msg = drmMessage;
} else {
errbuf = String8::format("%s: %s", msg, drmMessage);
- msg = errbuf.string();
+ msg = errbuf.c_str();
}
}
throwDrmStateException(env, msg, err);
diff --git a/media/jni/android_media_Streams.cpp b/media/jni/android_media_Streams.cpp
index 4fd5153..dffeb89 100644
--- a/media/jni/android_media_Streams.cpp
+++ b/media/jni/android_media_Streams.cpp
@@ -38,7 +38,7 @@
FileStream::FileStream(const String8 filename)
: mPosition(0) {
- mFile = fopen(filename.string(), "r");
+ mFile = fopen(filename.c_str(), "r");
if (mFile == NULL) {
return;
}
@@ -86,7 +86,7 @@
if (!piex::IsRaw(stream)) {
// Format not supported.
- ALOGV("Format not supported: %s", filename.string());
+ ALOGV("Format not supported: %s", filename.c_str());
return false;
}
@@ -94,7 +94,7 @@
if (err != piex::Error::kOk) {
// The input data seems to be broken.
- ALOGV("Raw image not detected: %s (piex error code: %d)", filename.string(), (int32_t)err);
+ ALOGV("Raw image not detected: %s (piex error code: %d)", filename.c_str(), (int32_t)err);
return false;
}
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 69cf804..1878716 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -118,7 +118,7 @@
// the string to return and advance the iterator for next time.
if (index < max) {
assetDir->mCachedFileName = assetDir->mAssetDir->getFileName(index);
- returnName = assetDir->mCachedFileName.string();
+ returnName = assetDir->mCachedFileName.c_str();
index++;
}
@@ -134,7 +134,7 @@
const char* AAssetDir_getFileName(AAssetDir* assetDir, int index)
{
assetDir->mCachedFileName = assetDir->mAssetDir->getFileName(index);
- return assetDir->mCachedFileName.string();
+ return assetDir->mCachedFileName.c_str();
}
void AAssetDir_close(AAssetDir* assetDir)
diff --git a/native/android/sensor.cpp b/native/android/sensor.cpp
index 968de34..bb8708b 100644
--- a/native/android/sensor.cpp
+++ b/native/android/sensor.cpp
@@ -304,12 +304,12 @@
const char* ASensor_getName(ASensor const* sensor) {
RETURN_IF_SENSOR_IS_NULL(nullptr);
- return static_cast<Sensor const*>(sensor)->getName().string();
+ return static_cast<Sensor const*>(sensor)->getName().c_str();
}
const char* ASensor_getVendor(ASensor const* sensor) {
RETURN_IF_SENSOR_IS_NULL(nullptr);
- return static_cast<Sensor const*>(sensor)->getVendor().string();
+ return static_cast<Sensor const*>(sensor)->getVendor().c_str();
}
int ASensor_getType(ASensor const* sensor) {
@@ -339,7 +339,7 @@
const char* ASensor_getStringType(ASensor const* sensor) {
RETURN_IF_SENSOR_IS_NULL(nullptr);
- return static_cast<Sensor const*>(sensor)->getStringType().string();
+ return static_cast<Sensor const*>(sensor)->getStringType().c_str();
}
int ASensor_getReportingMode(ASensor const* sensor) {
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index 294ca9c..6db87df 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -175,7 +175,7 @@
String16 filename16(filename);
String16 path16;
if (mMountService->getMountedObbPath(filename16, path16)) {
- return String8(path16).string();
+ return String8(path16).c_str();
} else {
return NULL;
}
@@ -183,7 +183,7 @@
};
void ObbActionListener::onObbResult(const android::String16& filename, const int32_t nonce, const int32_t state) {
- mStorageManager->fireCallback(String8(filename).string(), nonce, state);
+ mStorageManager->fireCallback(String8(filename).c_str(), nonce, state);
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0acce03..ec24ab7 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -954,9 +954,6 @@
<!-- UI debug setting: enable freeform window support summary [CHAR LIMIT=150] -->
<string name="enable_freeform_support_summary">Enable support for experimental freeform windows.</string>
- <!-- UI debug setting: enable desktop mode [CHAR LIMIT=25] -->
- <string name="desktop_mode">Desktop mode</string>
-
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
<!-- Summary text of the "local backup password" setting when the user has not supplied a password -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 5e66972..7669e79b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -3,7 +3,10 @@
hughchen@google.com
timhypeng@google.com
robertluo@google.com
-changbetty@google.com
songferngwang@google.com
+yqian@google.com
+chelseahao@google.com
+yiyishen@google.com
+hahong@google.com
# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
index 5c48c54..6b855c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrCodeGenerator.java
@@ -31,6 +31,7 @@
import java.util.Map;
public final class QrCodeGenerator {
+ private static final int DEFAULT_MARGIN = -1;
/**
* Generates a barcode image with {@code contents}.
*
@@ -40,7 +41,20 @@
*/
public static Bitmap encodeQrCode(String contents, int size)
throws WriterException, IllegalArgumentException {
- return encodeQrCode(contents, size, /*invert=*/false);
+ return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/false);
+ }
+
+ /**
+ * Generates a barcode image with {@code contents}.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param size The preferred image size in pixels
+ * @param margin The margin around the actual barcode
+ * @return Barcode bitmap
+ */
+ public static Bitmap encodeQrCode(String contents, int size, int margin)
+ throws WriterException, IllegalArgumentException {
+ return encodeQrCode(contents, size, margin, /*invert=*/false);
}
/**
@@ -53,10 +67,27 @@
*/
public static Bitmap encodeQrCode(String contents, int size, boolean invert)
throws WriterException, IllegalArgumentException {
+ return encodeQrCode(contents, size, DEFAULT_MARGIN, /*invert=*/invert);
+ }
+
+ /**
+ * Generates a barcode image with {@code contents}.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param size The preferred image size in pixels
+ * @param margin The margin around the actual barcode
+ * @param invert Whether to invert the black/white pixels (e.g. for dark mode)
+ * @return Barcode bitmap
+ */
+ public static Bitmap encodeQrCode(String contents, int size, int margin, boolean invert)
+ throws WriterException, IllegalArgumentException {
final Map<EncodeHintType, Object> hints = new HashMap<>();
if (!isIso88591(contents)) {
hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8.name());
}
+ if (margin != DEFAULT_MARGIN) {
+ hints.put(EncodeHintType.MARGIN, margin);
+ }
final BitMatrix qrBits = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE,
size, size, hints);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index fa2d677..1d25ac7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -106,5 +106,10 @@
Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV,
Settings.Global.Wearable.REDUCE_MOTION,
Settings.Global.Wearable.WEAR_LAUNCHER_UI_MODE,
+ Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
+ Settings.Global.Wearable.RSB_WAKE_ENABLED,
+ Settings.Global.Wearable.SCREENSHOT_ENABLED,
+ Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
+ Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
};
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 203efbf..c0f6231 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -98,7 +98,6 @@
Settings.System.VOLUME_VOICE, // deprecated since API 2?
Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
- Settings.System.DESKTOP_MODE, // developer setting for internal prototyping
Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
Settings.System.SCREEN_BRIGHTNESS_FLOAT,
@@ -658,7 +657,6 @@
Settings.Global.Wearable.COMPANION_BLE_ROLE,
Settings.Global.Wearable.COMPANION_NAME,
Settings.Global.Wearable.COMPANION_APP_NAME,
- Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
Settings.Global.Wearable.COMPANION_OS_VERSION,
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
Settings.Global.Wearable.SETUP_LOCALE,
@@ -673,16 +671,12 @@
Settings.Global.Wearable.CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED,
Settings.Global.Wearable.WET_MODE_ON,
Settings.Global.Wearable.COOLDOWN_MODE_ON,
- Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
- Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
- Settings.Global.Wearable.RSB_WAKE_ENABLED,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
- Settings.Global.Wearable.SCREENSHOT_ENABLED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2077af8..c92fe22 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -326,6 +326,17 @@
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt",
+
+ /* Bouncer UI tests */
+ "tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java",
+ "tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java",
+ "tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java",
+ "tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt",
+ "tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt",
],
path: "tests/src",
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6778d5a..b5b873c 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -349,6 +349,9 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <!-- Listen to (dis-)connection of external displays and enable / disable them. -->
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
index d9a45cd..2069ebd 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -19,18 +19,25 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
+import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -70,23 +77,38 @@
// the same as SwipeableV2Defaults.PositionalThreshold.
val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
- return draggable(
- orientation = orientation,
- enabled = enabled,
- startDragImmediately = startDragImmediately,
- onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
- state =
- rememberDraggableState { delta -> onDrag(layoutImpl, transition, orientation, delta) },
- onDragStopped = { velocity ->
- onDragStopped(
- layoutImpl,
- transition,
- velocity,
- velocityThreshold,
- positionalThreshold,
- )
- },
- )
+ val draggableState = rememberDraggableState { delta ->
+ onDrag(layoutImpl, transition, orientation, delta)
+ }
+
+ return nestedScroll(
+ connection =
+ rememberSwipeToSceneNestedScrollConnection(
+ orientation = orientation,
+ coroutineScope = rememberCoroutineScope(),
+ draggableState = draggableState,
+ transition = transition,
+ layoutImpl = layoutImpl,
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold
+ ),
+ )
+ .draggable(
+ state = draggableState,
+ orientation = orientation,
+ enabled = enabled,
+ startDragImmediately = startDragImmediately,
+ onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
+ onDragStopped = { velocity ->
+ onDragStopped(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocity,
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold,
+ )
+ },
+ )
}
private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
@@ -235,35 +257,18 @@
// twice in a row to accelerate the transition and go from A => B then B => C really fast.
maybeHandleAcceleratedSwipe(transition, orientation)
- val fromScene = transition._fromScene
- val upOrLeft = fromScene.upOrLeft(orientation)
- val downOrRight = fromScene.downOrRight(orientation)
val offset = transition.dragOffset
+ val fromScene = transition._fromScene
// Compute the target scene depending on the current offset.
- val targetSceneKey: SceneKey
- val signedDistance: Float
- when {
- offset < 0f && upOrLeft != null -> {
- targetSceneKey = upOrLeft
- signedDistance = -transition.absoluteDistance
- }
- offset > 0f && downOrRight != null -> {
- targetSceneKey = downOrRight
- signedDistance = transition.absoluteDistance
- }
- else -> {
- targetSceneKey = fromScene.key
- signedDistance = 0f
- }
+ val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
+
+ if (transition._toScene.key != target.sceneKey) {
+ transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
}
- if (transition._toScene.key != targetSceneKey) {
- transition._toScene = layoutImpl.scenes.getValue(targetSceneKey)
- }
-
- if (transition._distance != signedDistance) {
- transition._distance = signedDistance
+ if (transition._distance != target.distance) {
+ transition._distance = target.distance
}
}
@@ -299,12 +304,55 @@
// using fromScene and dragOffset.
}
+private data class TargetScene(
+ val sceneKey: SceneKey,
+ val distance: Float,
+)
+
+private fun Scene.findTargetSceneAndDistance(
+ orientation: Orientation,
+ directionOffset: Float,
+ layoutImpl: SceneTransitionLayoutImpl,
+): TargetScene {
+ val maxDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
+
+ val upOrLeft = upOrLeft(orientation)
+ val downOrRight = downOrRight(orientation)
+
+ // Compute the target scene depending on the current offset.
+ return when {
+ directionOffset < 0f && upOrLeft != null -> {
+ TargetScene(
+ sceneKey = upOrLeft,
+ distance = -maxDistance,
+ )
+ }
+ directionOffset > 0f && downOrRight != null -> {
+ TargetScene(
+ sceneKey = downOrRight,
+ distance = maxDistance,
+ )
+ }
+ else -> {
+ TargetScene(
+ sceneKey = key,
+ distance = 0f,
+ )
+ }
+ }
+}
+
private fun CoroutineScope.onDragStopped(
layoutImpl: SceneTransitionLayoutImpl,
transition: SwipeTransition,
velocity: Float,
velocityThreshold: Float,
positionalThreshold: Float,
+ canChangeScene: Boolean = true,
) {
// The state was changed since the drag started; don't do anything.
if (layoutImpl.state.transitionState != transition) {
@@ -323,14 +371,15 @@
val offset = transition.dragOffset
val distance = transition.distance
if (
- shouldCommitSwipe(
- offset,
- distance,
- velocity,
- velocityThreshold,
- positionalThreshold,
- wasCommitted = transition._currentScene == transition._toScene,
- )
+ canChangeScene &&
+ shouldCommitSwipe(
+ offset,
+ distance,
+ velocity,
+ velocityThreshold,
+ positionalThreshold,
+ wasCommitted = transition._currentScene == transition._toScene,
+ )
) {
targetOffset = distance
targetScene = transition._toScene
@@ -348,31 +397,13 @@
layoutImpl.onChangeScene(targetScene.key)
}
- // Animate the offset.
- transition.offsetAnimationJob = launch {
- transition.offsetAnimatable.snapTo(offset)
- transition.isAnimatingOffset = true
-
- transition.offsetAnimatable.animateTo(
- targetOffset,
- // TODO(b/290184746): Make this spring spec configurable.
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = OffsetVisibilityThreshold
- ),
- initialVelocity = velocity,
- )
-
- // Now that the animation is done, the state should be idle. Note that if the state was
- // changed since this animation started, some external code changed it and we shouldn't do
- // anything here. Note also that this job will be cancelled in the case where the user
- // intercepts this swipe.
- if (layoutImpl.state.transitionState == transition) {
- layoutImpl.state.transitionState = TransitionState.Idle(targetScene.key)
- }
-
- transition.offsetAnimationJob = null
- }
+ animateOffset(
+ transition = transition,
+ layoutImpl = layoutImpl,
+ initialVelocity = velocity,
+ targetOffset = targetOffset,
+ targetScene = targetScene.key
+ )
}
/**
@@ -412,8 +443,216 @@
}
}
+private fun CoroutineScope.animateOffset(
+ transition: SwipeTransition,
+ layoutImpl: SceneTransitionLayoutImpl,
+ initialVelocity: Float,
+ targetOffset: Float,
+ targetScene: SceneKey,
+) {
+ transition.offsetAnimationJob = launch {
+ if (!transition.isAnimatingOffset) {
+ transition.offsetAnimatable.snapTo(transition.dragOffset)
+ }
+ transition.isAnimatingOffset = true
+
+ transition.offsetAnimatable.animateTo(
+ targetOffset,
+ // TODO(b/290184746): Make this spring spec configurable.
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = OffsetVisibilityThreshold
+ ),
+ initialVelocity = initialVelocity,
+ )
+
+ // Now that the animation is done, the state should be idle. Note that if the state was
+ // changed since this animation started, some external code changed it and we shouldn't do
+ // anything here. Note also that this job will be cancelled in the case where the user
+ // intercepts this swipe.
+ if (layoutImpl.state.transitionState == transition) {
+ layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+ }
+
+ transition.offsetAnimationJob = null
+ }
+}
+
+private fun CoroutineScope.animateOverscroll(
+ layoutImpl: SceneTransitionLayoutImpl,
+ transition: SwipeTransition,
+ velocity: Velocity,
+ orientation: Orientation,
+): Velocity {
+ val velocityAmount =
+ when (orientation) {
+ Orientation.Vertical -> velocity.y
+ Orientation.Horizontal -> velocity.x
+ }
+
+ if (velocityAmount == 0f) {
+ // There is no remaining velocity
+ return Velocity.Zero
+ }
+
+ val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
+ val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+
+ if (!isValidTarget || layoutImpl.state.transitionState == transition) {
+ // We have not found a valid target or we are already in a transition
+ return Velocity.Zero
+ }
+
+ transition._currentScene = fromScene
+ transition._fromScene = fromScene
+ transition._toScene = layoutImpl.scene(target.sceneKey)
+ transition._distance = target.distance
+ transition.absoluteDistance = target.distance.absoluteValue
+ transition.dragOffset = 0f
+ transition.isAnimatingOffset = false
+ transition.offsetAnimationJob = null
+
+ layoutImpl.state.transitionState = transition
+
+ animateOffset(
+ transition = transition,
+ layoutImpl = layoutImpl,
+ initialVelocity = velocityAmount,
+ targetOffset = 0f,
+ targetScene = fromScene.key
+ )
+
+ // The animateOffset animation consumes any remaining velocity.
+ return velocity
+}
+
/**
* The number of pixels below which there won't be a visible difference in the transition and from
* which the animation can stop.
*/
private const val OffsetVisibilityThreshold = 0.5f
+
+@Composable
+private fun rememberSwipeToSceneNestedScrollConnection(
+ orientation: Orientation,
+ coroutineScope: CoroutineScope,
+ draggableState: DraggableState,
+ transition: SwipeTransition,
+ layoutImpl: SceneTransitionLayoutImpl,
+ velocityThreshold: Float,
+ positionalThreshold: Float,
+): PriorityPostNestedScrollConnection {
+ val density = LocalDensity.current
+ val scrollConnection =
+ remember(
+ orientation,
+ coroutineScope,
+ draggableState,
+ transition,
+ layoutImpl,
+ velocityThreshold,
+ positionalThreshold,
+ density,
+ ) {
+ fun Offset.toAmount() =
+ when (orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
+
+ fun Velocity.toAmount() =
+ when (orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
+
+ fun Float.toOffset() =
+ when (orientation) {
+ Orientation.Horizontal -> Offset(x = this, y = 0f)
+ Orientation.Vertical -> Offset(x = 0f, y = this)
+ }
+
+ // The next potential scene is calculated during the canStart
+ var nextScene: SceneKey? = null
+
+ // This is the scene on which we will have priority during the scroll gesture.
+ var priorityScene: SceneKey? = null
+
+ // If we performed a long gesture before entering priority mode, we would have to avoid
+ // moving on to the next scene.
+ var gestureStartedOnNestedChild = false
+
+ PriorityPostNestedScrollConnection(
+ canStart = { offsetAvailable, offsetBeforeStart ->
+ val amount = offsetAvailable.toAmount()
+ if (amount == 0f) return@PriorityPostNestedScrollConnection false
+
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+ val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ nextScene =
+ when {
+ amount < 0f -> fromScene.upOrLeft(orientation)
+ amount > 0f -> fromScene.downOrRight(orientation)
+ else -> null
+ }
+
+ nextScene != null
+ },
+ canContinueScroll = { priorityScene == transition._toScene.key },
+ onStart = {
+ priorityScene = nextScene
+ onDragStarted(layoutImpl, transition, orientation)
+ },
+ onScroll = { offsetAvailable ->
+ val amount = offsetAvailable.toAmount()
+
+ // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
+ // is initiated in a nested child.
+
+ // Appends a new coroutine to attempt to drag by [amount] px. In this case we
+ // are assuming that the [coroutineScope] is tied to the main thread and that
+ // calls to [launch] are therefore queued.
+ coroutineScope.launch { draggableState.drag { dragBy(amount) } }
+
+ amount.toOffset()
+ },
+ onStop = { velocityAvailable ->
+ priorityScene = null
+
+ coroutineScope.onDragStopped(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocityAvailable.toAmount(),
+ velocityThreshold = velocityThreshold,
+ positionalThreshold = positionalThreshold,
+ canChangeScene = !gestureStartedOnNestedChild
+ )
+
+ // The onDragStopped animation consumes any remaining velocity.
+ velocityAvailable
+ },
+ onPostFling = { velocityAvailable ->
+ // If there is any velocity left, we can try running an overscroll animation
+ // between scenes.
+ coroutineScope.animateOverscroll(
+ layoutImpl = layoutImpl,
+ transition = transition,
+ velocity = velocityAvailable,
+ orientation = orientation
+ )
+ },
+ )
+ }
+ DisposableEffect(scrollConnection) {
+ onDispose {
+ coroutineScope.launch {
+ // This should ensure that the draggableState is in a consistent state and that it
+ // does not cause any unexpected behavior.
+ scrollConnection.reset()
+ }
+ }
+ }
+ return scrollConnection
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
new file mode 100644
index 0000000..cea8d9a
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+
+/**
+ * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
+ * following way:
+ * - If you **scroll up**, it **first brings the [height]** back to the [minHeight] and then allows
+ * scrolling of the children (usually the content).
+ * - If you **scroll down**, it **first allows scrolling of the children** (usually the content) and
+ * then resets the [height] to [maxHeight].
+ *
+ * This behavior is useful for implementing a
+ * [Large top app bar](https://m3.material.io/components/top-app-bar/specs) effect or something
+ * similar.
+ *
+ * @sample com.android.compose.animation.scene.demo.Shade
+ */
+class LargeTopAppBarNestedScrollConnection(
+ private val height: () -> Float,
+ private val onChangeHeight: (Float) -> Unit,
+ private val minHeight: Float,
+ private val maxHeight: Float,
+) : NestedScrollConnection {
+
+ constructor(
+ height: () -> Float,
+ onHeightChanged: (Float) -> Unit,
+ heightRange: ClosedFloatingPointRange<Float>,
+ ) : this(
+ height = height,
+ onChangeHeight = onHeightChanged,
+ minHeight = heightRange.start,
+ maxHeight = heightRange.endInclusive,
+ )
+
+ /**
+ * When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will expand.
+ * Then, you can then scroll down the content.
+ */
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ val y = available.y
+ val currentHeight = height()
+ if (y >= 0 || currentHeight <= minHeight) {
+ return Offset.Zero
+ }
+
+ val amountLeft = minHeight - currentHeight
+ val amountConsumed = y.coerceAtLeast(amountLeft)
+ onChangeHeight(currentHeight + amountConsumed)
+ return Offset(0f, amountConsumed)
+ }
+
+ /**
+ * When swiping down, the content will scroll up until it reaches the top. Then, the
+ * LargeTopAppBar will expand until it reaches its [maxHeight].
+ */
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ val y = available.y
+ val currentHeight = height()
+ if (y <= 0 || currentHeight >= maxHeight) {
+ return Offset.Zero
+ }
+
+ val amountLeft = maxHeight - currentHeight
+ val amountConsumed = y.coerceAtMost(amountLeft)
+ onChangeHeight(currentHeight + amountConsumed)
+ return Offset(0f, amountConsumed)
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
new file mode 100644
index 0000000..793a9a5
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
+ * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
+ * until [canContinueScroll] allows it.
+ *
+ * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
+ * after [onStart].
+ *
+ * @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
+ */
+class PriorityPostNestedScrollConnection(
+ private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canContinueScroll: () -> Boolean,
+ private val onStart: () -> Unit,
+ private val onScroll: (offsetAvailable: Offset) -> Offset,
+ private val onStop: (velocityAvailable: Velocity) -> Velocity,
+ private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
+) : NestedScrollConnection {
+
+ /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
+ private var isPriorityMode = false
+
+ private var offsetScrolledBeforePriorityMode = Offset.Zero
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource,
+ ): Offset {
+ // The offset before the start takes into account the up and down movements, starting from
+ // the beginning or from the last fling gesture.
+ val offsetBeforeStart = offsetScrolledBeforePriorityMode - available
+
+ if (
+ isPriorityMode ||
+ source == NestedScrollSource.Fling ||
+ !canStart(available, offsetBeforeStart)
+ ) {
+ // The priority mode cannot start so we won't consume the available offset.
+ return Offset.Zero
+ }
+
+ // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+ // available offset following a scroll event.
+ isPriorityMode = true
+
+ // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+ // lifted (step 3b), or this object has been destroyed (step 3c).
+ onStart()
+
+ return onScroll(available)
+ }
+
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ if (!isPriorityMode) {
+ if (source != NestedScrollSource.Fling) {
+ // We want to track the amount of offset consumed before entering priority mode
+ offsetScrolledBeforePriorityMode += available
+ }
+
+ return Offset.Zero
+ }
+
+ if (!canContinueScroll()) {
+ // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+ onPriorityStop(velocity = Velocity.Zero)
+ return Offset.Zero
+ }
+
+ // Step 2: We have the priority and can consume the scroll events.
+ return onScroll(available)
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
+ // of the fling gesture.
+ return onPriorityStop(velocity = available)
+ }
+
+ override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+ return onPostFling(available)
+ }
+
+ /** Method to call before destroying the object or to reset the initial state. */
+ fun reset() {
+ // Step 3c: To ensure that an onStop is always called for every onStart.
+ onPriorityStop(velocity = Velocity.Zero)
+ }
+
+ private fun onPriorityStop(velocity: Velocity): Velocity {
+
+ // We can restart tracking the consumed offsets from scratch.
+ offsetScrolledBeforePriorityMode = Offset.Zero
+
+ if (!isPriorityMode) {
+ return Velocity.Zero
+ }
+
+ isPriorityMode = false
+
+ return onStop(velocity)
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
new file mode 100644
index 0000000..03d231a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 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.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
+ val scrollSource = testCase.scrollSource
+
+ private var height = 0f
+
+ private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
+ LargeTopAppBarNestedScrollConnection(
+ height = { height },
+ onHeightChanged = { height = it },
+ heightRange = heightRange,
+ )
+
+ @Test
+ fun onScrollUp_consumeHeightFirst() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+ // It can decrease by 1 the height
+ assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f))
+ assertThat(height).isEqualTo(0f)
+ }
+
+ @Test
+ fun onScrollUp_consumeDownToMin() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 0f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource)
+
+ // It should not change the height (already at min)
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(0f)
+ }
+
+ @Test
+ fun onScrollUp_ignorePostScroll() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = -1f),
+ source = scrollSource
+ )
+
+ // It should ignore all onPostScroll events
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(1f)
+ }
+
+ @Test
+ fun onScrollDown_allowConsumeContentFirst() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = scrollSource)
+
+ // It should ignore all onPreScroll events
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(1f)
+ }
+
+ @Test
+ fun onScrollDown_consumeHeightPostScroll() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 1f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = 1f),
+ source = scrollSource
+ )
+
+ // It can increase by 1 the height
+ assertThat(offsetConsumed).isEqualTo(Offset(0f, 1f))
+ assertThat(height).isEqualTo(2f)
+ }
+
+ @Test
+ fun onScrollDown_consumeUpToMax() {
+ val scrollConnection = buildScrollConnection(heightRange = 0f..2f)
+ height = 2f
+
+ val offsetConsumed =
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset(x = 0f, y = 1f),
+ source = scrollSource
+ )
+
+ // It should not change the height (already at max)
+ assertThat(offsetConsumed).isEqualTo(Offset.Zero)
+ assertThat(height).isEqualTo(2f)
+ }
+
+ // NestedScroll Source is a value/inline class and must be wrapped in a parameterized test
+ // https://youtrack.jetbrains.com/issue/KT-35523/Parameterized-JUnit-tests-with-inline-classes-throw-IllegalArgumentException
+ data class TestCase(val scrollSource: NestedScrollSource) {
+ override fun toString() = scrollSource.toString()
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun data(): List<TestCase> =
+ listOf(
+ TestCase(NestedScrollSource.Drag),
+ TestCase(NestedScrollSource.Fling),
+ TestCase(NestedScrollSource.Wheel),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
new file mode 100644
index 0000000..8e2b77a
--- /dev/null
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.compose.nestedscroll
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PriorityPostNestedScrollConnectionTest {
+ private var canStart = false
+ private var canContinueScroll = false
+ private var isStarted = false
+ private var lastScroll: Offset? = null
+ private var returnOnScroll = Offset.Zero
+ private var lastStop: Velocity? = null
+ private var returnOnStop = Velocity.Zero
+ private var lastOnPostFling: Velocity? = null
+ private var returnOnPostFling = Velocity.Zero
+
+ private val scrollConnection =
+ PriorityPostNestedScrollConnection(
+ canStart = { _, _ -> canStart },
+ canContinueScroll = { canContinueScroll },
+ onStart = { isStarted = true },
+ onScroll = {
+ lastScroll = it
+ returnOnScroll
+ },
+ onStop = {
+ lastStop = it
+ returnOnStop
+ },
+ onPostFling = {
+ lastOnPostFling = it
+ returnOnPostFling
+ },
+ )
+
+ private val offset1 = Offset(1f, 1f)
+ private val offset2 = Offset(2f, 2f)
+ private val velocity1 = Velocity(1f, 1f)
+ private val velocity2 = Velocity(2f, 2f)
+
+ private fun startPriorityMode() {
+ canStart = true
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
+ canStart = true
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ startPriorityMode()
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyIfAllowed() {
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ startPriorityMode()
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ @Test
+ fun step1_onPriorityModeStarted_receiveAvailableOffset() {
+ canStart = true
+
+ scrollConnection.onPostScroll(
+ consumed = offset1,
+ available = offset2,
+ source = NestedScrollSource.Drag
+ )
+
+ assertThat(lastScroll).isEqualTo(offset2)
+ }
+
+ @Test
+ fun step2_onPriorityMode_shouldContinueIfAllowed() {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
+ assertThat(lastScroll).isEqualTo(offset1)
+
+ canContinueScroll = false
+ scrollConnection.onPreScroll(available = offset2, source = NestedScrollSource.Drag)
+ assertThat(lastScroll).isNotEqualTo(offset2)
+ assertThat(lastScroll).isEqualTo(offset1)
+ }
+
+ @Test
+ fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
+ startPriorityMode()
+ canContinueScroll = false
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun step3c_onPriorityMode_shouldStopOnReset() {
+ startPriorityMode()
+ canContinueScroll = true
+
+ scrollConnection.reset()
+
+ assertThat(lastStop).isNotNull()
+ }
+
+ @Test
+ fun receive_onPostFling() = runTest {
+ scrollConnection.onPostFling(
+ consumed = velocity1,
+ available = velocity2,
+ )
+
+ assertThat(lastOnPostFling).isEqualTo(velocity2)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index 7545ff4..8a8557a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -21,7 +21,6 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
@@ -45,7 +44,6 @@
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
/** UI for the input part of a password-requiring version of the bouncer. */
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun PasswordBouncer(
viewModel: PasswordBouncerViewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 64227b8..03efbe0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -21,6 +21,8 @@
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -199,33 +201,39 @@
.onSizeChanged { containerSize = it }
.thenIf(isInputEnabled) {
Modifier.pointerInput(Unit) {
- detectDragGestures(
- onDragStart = { start ->
- inputPosition = start
- viewModel.onDragStart()
- },
- onDragEnd = {
- inputPosition = null
- if (isAnimationEnabled) {
- lineFadeOutAnimatables.values.forEach { animatable ->
- // Launch using the longer-lived scope because we want these
- // animations to proceed to completion even if the surrounding
- // scope is canceled.
- scope.launch { animatable.animateTo(1f) }
- }
- }
- viewModel.onDragEnd()
- },
- ) { change, _ ->
- inputPosition = change.position
- viewModel.onDrag(
- xPx = change.position.x,
- yPx = change.position.y,
- containerSizePx = containerSize.width,
- verticalOffsetPx = verticalOffset,
- )
+ awaitEachGesture {
+ awaitFirstDown()
+ viewModel.onDown()
+ }
}
- }
+ .pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = { start ->
+ inputPosition = start
+ viewModel.onDragStart()
+ },
+ onDragEnd = {
+ inputPosition = null
+ if (isAnimationEnabled) {
+ lineFadeOutAnimatables.values.forEach { animatable ->
+ // Launch using the longer-lived scope because we want these
+ // animations to proceed to completion even if the
+ // surrounding scope is canceled.
+ scope.launch { animatable.animateTo(1f) }
+ }
+ }
+ viewModel.onDragEnd()
+ },
+ ) { change, _ ->
+ inputPosition = change.position
+ viewModel.onDrag(
+ xPx = change.position.x,
+ yPx = change.position.y,
+ containerSizePx = containerSize.width,
+ verticalOffsetPx = verticalOffset,
+ )
+ }
+ }
}
) {
if (isAnimationEnabled) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index ec6e5ed..e5c6977 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -24,6 +24,8 @@
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -76,7 +78,13 @@
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- modifier = modifier,
+ modifier =
+ modifier.pointerInput(Unit) {
+ awaitEachGesture {
+ awaitFirstDown()
+ viewModel.onDown()
+ }
+ }
) {
PinInputDisplay(viewModel)
Spacer(Modifier.height(100.dp))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 48f40e7..418df5c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -19,13 +19,11 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.LocalContentColor
@@ -35,9 +33,13 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
+import kotlin.math.roundToInt
/**
* The content of an AlertDialog which can be used together with
@@ -99,28 +101,101 @@
Spacer(Modifier.height(32.dp))
// Buttons.
- // TODO(b/283817398): If there is not enough space, the buttons should automatically stack
- // as shown in go/sysui-dialog-styling.
if (positiveButton != null || negativeButton != null || neutralButton != null) {
- Row(Modifier.fillMaxWidth()) {
- if (neutralButton != null) {
- neutralButton()
- Spacer(Modifier.width(8.dp))
- }
+ AlertDialogButtons(
+ positiveButton = positiveButton,
+ negativeButton = negativeButton,
+ neutralButton = neutralButton,
+ )
+ }
+ }
+}
- Spacer(Modifier.weight(1f))
+@Composable
+private fun AlertDialogButtons(
+ positiveButton: (@Composable () -> Unit)?,
+ negativeButton: (@Composable () -> Unit)?,
+ neutralButton: (@Composable () -> Unit)?,
+ modifier: Modifier = Modifier,
+) {
+ Layout(
+ content = {
+ positiveButton?.let { Box(Modifier.layoutId("positive")) { it() } }
+ negativeButton?.let { Box(Modifier.layoutId("negative")) { it() } }
+ neutralButton?.let { Box(Modifier.layoutId("neutral")) { it() } }
+ },
+ modifier,
+ ) { measurables, constraints ->
+ check(constraints.hasBoundedWidth) {
+ "AlertDialogButtons should not be composed in an horizontally scrollable layout"
+ }
+ val maxWidth = constraints.maxWidth
- if (negativeButton != null) {
- negativeButton()
- }
+ // Measure the buttons.
+ var positive: Placeable? = null
+ var negative: Placeable? = null
+ var neutral: Placeable? = null
+ for (i in measurables.indices) {
+ val measurable = measurables[i]
+ when (val layoutId = measurable.layoutId) {
+ "positive" -> positive = measurable.measure(constraints)
+ "negative" -> negative = measurable.measure(constraints)
+ "neutral" -> neutral = measurable.measure(constraints)
+ else -> error("Unexpected layoutId=$layoutId")
+ }
+ }
- if (positiveButton != null) {
- if (negativeButton != null) {
- Spacer(Modifier.width(8.dp))
+ fun Placeable?.width() = this?.width ?: 0
+ fun Placeable?.height() = this?.height ?: 0
+
+ // The min horizontal spacing between buttons.
+ val horizontalSpacing = 8.dp.toPx()
+ val totalHorizontalSpacing = (measurables.size - 1) * horizontalSpacing
+ val requiredWidth =
+ positive.width() + negative.width() + neutral.width() + totalHorizontalSpacing
+
+ if (requiredWidth <= maxWidth) {
+ // Stack horizontally: [neutral][flexSpace][negative][positive].
+ val height = maxOf(positive.height(), negative.height(), neutral.height())
+ layout(maxWidth, height) {
+ positive?.let { it.placeRelative(maxWidth - it.width, 0) }
+
+ negative?.let { negative ->
+ if (positive == null) {
+ negative.placeRelative(maxWidth - negative.width, 0)
+ } else {
+ negative.placeRelative(
+ maxWidth -
+ negative.width -
+ positive.width -
+ horizontalSpacing.roundToInt(),
+ 0
+ )
}
-
- positiveButton()
}
+
+ neutral?.placeRelative(0, 0)
+ }
+ } else {
+ // Stack vertically, aligned on the right (in LTR layouts):
+ // [positive]
+ // [negative]
+ // [neutral]
+ //
+ // TODO(b/283817398): Introduce a ResponsiveDialogButtons composable to create buttons
+ // that have different styles when stacked horizontally, as shown in
+ // go/sysui-dialog-styling.
+ val height = positive.height() + negative.height() + neutral.height()
+ layout(maxWidth, height) {
+ var y = 0
+ fun Placeable.place() {
+ placeRelative(maxWidth - width, y)
+ y += this.height
+ }
+
+ positive?.place()
+ negative?.place()
+ neutral?.place()
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 0a100ba..463253b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -14,20 +14,33 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
package com.android.systemui.keyguard.ui.composable
import android.view.View
import android.view.ViewGroup
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.toComposeRect
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -67,8 +80,8 @@
modifier: Modifier,
) {
LockscreenScene(
- viewModel = viewModel,
viewProvider = viewProvider,
+ longPressViewModel = viewModel.longPress,
modifier = modifier,
)
}
@@ -85,23 +98,68 @@
@Composable
private fun LockscreenScene(
- viewModel: LockscreenSceneViewModel,
viewProvider: () -> View,
+ longPressViewModel: KeyguardLongPressViewModel,
modifier: Modifier = Modifier,
) {
- AndroidView(
- factory = { _ ->
- val keyguardRootView = viewProvider()
- // Remove the KeyguardRootView from any parent it might already have in legacy code just
- // in case (a view can't have two parents).
- (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
- keyguardRootView
- },
- update = { keyguardRootView ->
- keyguardRootView.requireViewById<View>(R.id.lock_icon_view).setOnClickListener {
- viewModel.onLockButtonClicked()
- }
- },
+ fun findSettingsMenu(): View {
+ return viewProvider().requireViewById(R.id.keyguard_settings_button)
+ }
+
+ Box(
modifier = modifier,
+ ) {
+ LongPressSurface(
+ viewModel = longPressViewModel,
+ isSettingsMenuVisible = { findSettingsMenu().isVisible },
+ settingsMenuBounds = {
+ val bounds = android.graphics.Rect()
+ findSettingsMenu().getHitRect(bounds)
+ bounds.toComposeRect()
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+
+ AndroidView(
+ factory = { _ ->
+ val keyguardRootView = viewProvider()
+ // Remove the KeyguardRootView from any parent it might already have in legacy code
+ // just in case (a view can't have two parents).
+ (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
+ keyguardRootView
+ },
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+}
+
+@Composable
+private fun LongPressSurface(
+ viewModel: KeyguardLongPressViewModel,
+ isSettingsMenuVisible: () -> Boolean,
+ settingsMenuBounds: () -> Rect,
+ modifier: Modifier = Modifier,
+) {
+ val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false)
+
+ Box(
+ modifier =
+ modifier
+ .combinedClickable(
+ enabled = isEnabled,
+ onLongClick = viewModel::onLongPress,
+ onClick = {},
+ )
+ .pointerInput(Unit) {
+ awaitEachGesture {
+ val pointerInputChange = awaitFirstDown()
+ if (
+ isSettingsMenuVisible() &&
+ !settingsMenuBounds().contains(pointerInputChange.position)
+ ) {
+ viewModel.onTouchedOutside()
+ }
+ }
+ },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 774c409..40b0b4a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,11 +17,7 @@
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
@@ -54,17 +50,6 @@
override fun SceneScope.Content(
modifier: Modifier,
) {
- /*
- * TODO(b/279501596): once we start testing with the real Content Dynamics Framework,
- * replace this with an error to make sure it doesn't get rendered.
- */
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Gone", style = MaterialTheme.typography.headlineMedium)
- }
- }
+ Box(modifier = modifier)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index ffb20d8..31cbcb9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalComposeUiApi::class)
+
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.fillMaxSize
@@ -25,6 +27,9 @@
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.input.pointer.pointerInput
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ObservableTransitionState as SceneTransitionObservableTransitionState
import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
@@ -82,7 +87,18 @@
onChangeScene = viewModel::onSceneChanged,
transitions = SceneContainerTransitions,
state = state,
- modifier = modifier.fillMaxSize().motionEventSpy { viewModel.onUserInput() },
+ modifier =
+ modifier
+ .fillMaxSize()
+ .motionEventSpy { event -> viewModel.onMotionEvent(event) }
+ .pointerInput(Unit) {
+ awaitPointerEventScope {
+ while (true) {
+ awaitPointerEvent(PointerEventPass.Final)
+ viewModel.onMotionEventComplete()
+ }
+ }
+ }
) {
sceneByKey.forEach { (sceneKey, composableScene) ->
scene(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 966e183..054f9ec 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -68,11 +68,11 @@
private fun <TKey : Any, TVal : Any> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
key: TKey,
value: TVal,
- onNew: () -> Unit
+ onNew: (TVal) -> Unit
): TVal {
val result = this.putIfAbsent(key, value)
if (result == null) {
- onNew()
+ onNew(value)
}
return result ?: value
}
@@ -148,6 +148,8 @@
override fun onPluginAttached(
manager: PluginLifecycleManager<ClockProviderPlugin>
): Boolean {
+ manager.isDebug = true
+
if (keepAllLoaded) {
// Always load new plugins if requested
return true
@@ -177,15 +179,22 @@
val info =
availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
isClockListChanged = true
- onConnected(id)
+ onConnected(it)
}
if (manager != info.manager) {
logger.tryLog(
TAG,
LogLevel.ERROR,
- { str1 = id },
- { "Clock Id conflict on known attach: $str1 is double registered" }
+ {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ },
+ {
+ "Clock Id conflict on attach: " +
+ "$str1 is double registered by $str2 and $str3"
+ }
)
continue
}
@@ -217,22 +226,29 @@
val info =
availableClocks.concurrentGetOrPut(id, ClockInfo(clock, plugin, manager)) {
isClockListChanged = true
- onConnected(id)
+ onConnected(it)
}
if (manager != info.manager) {
logger.tryLog(
TAG,
LogLevel.ERROR,
- { str1 = id },
- { "Clock Id conflict on load: $str1 is double registered" }
+ {
+ str1 = id
+ str2 = info.manager.toString()
+ str3 = manager.toString()
+ },
+ {
+ "Clock Id conflict on load: " +
+ "$str1 is double registered by $str2 and $str3"
+ }
)
manager.unloadPlugin()
continue
}
info.provider = plugin
- onLoaded(id)
+ onLoaded(info)
}
if (isClockListChanged) {
@@ -252,26 +268,33 @@
logger.tryLog(
TAG,
LogLevel.ERROR,
- { str1 = id },
- { "Clock Id conflict on unload: $str1 is double registered" }
+ {
+ str1 = id
+ str2 = info?.manager.toString()
+ str3 = manager.toString()
+ },
+ {
+ "Clock Id conflict on unload: " +
+ "$str1 is double registered by $str2 and $str3"
+ }
)
continue
}
info.provider = null
- onUnloaded(id)
+ onUnloaded(info)
}
verifyLoadedProviders()
}
override fun onPluginDetached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
- val removed = mutableListOf<ClockId>()
+ val removed = mutableListOf<ClockInfo>()
availableClocks.entries.removeAll {
if (it.value.manager != manager) {
return@removeAll false
}
- removed.add(it.key)
+ removed.add(it.value)
return@removeAll true
}
@@ -493,6 +516,12 @@
scope.launch(bgDispatcher) {
if (keepAllLoaded) {
+ logger.tryLog(
+ TAG,
+ LogLevel.INFO,
+ {},
+ { "verifyLoadedProviders: keepAllLoaded=true" }
+ )
// Enforce that all plugins are loaded if requested
for ((_, info) in availableClocks) {
info.manager?.loadPlugin()
@@ -503,6 +532,12 @@
val currentClock = availableClocks[currentClockId]
if (currentClock == null) {
+ logger.tryLog(
+ TAG,
+ LogLevel.INFO,
+ {},
+ { "verifyLoadedProviders: currentClock=null" }
+ )
// Current Clock missing, load no plugins and use default
for ((_, info) in availableClocks) {
info.manager?.unloadPlugin()
@@ -511,12 +546,13 @@
return@launch
}
+ logger.tryLog(TAG, LogLevel.INFO, {}, { "verifyLoadedProviders: load currentClock" })
val currentManager = currentClock.manager
currentManager?.loadPlugin()
for ((_, info) in availableClocks) {
val manager = info.manager
- if (manager != null && manager.isLoaded && currentManager != manager) {
+ if (manager != null && currentManager != manager) {
manager.unloadPlugin()
}
}
@@ -524,57 +560,68 @@
}
}
- private fun onConnected(clockId: ClockId) {
- logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" })
- if (currentClockId == clockId) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- { str1 = clockId },
- { "Current clock ($str1) was connected" }
- )
- }
+ private fun onConnected(info: ClockInfo) {
+ val isCurrent = currentClockId == info.metadata.clockId
+ logger.tryLog(
+ TAG,
+ if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+ {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ },
+ { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ )
}
- private fun onLoaded(clockId: ClockId) {
- logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" })
+ private fun onLoaded(info: ClockInfo) {
+ val isCurrent = currentClockId == info.metadata.clockId
+ logger.tryLog(
+ TAG,
+ if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+ {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ },
+ { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ )
- if (currentClockId == clockId) {
- logger.tryLog(
- TAG,
- LogLevel.INFO,
- { str1 = clockId },
- { "Current clock ($str1) was loaded" }
- )
+ if (isCurrent) {
triggerOnCurrentClockChanged()
}
}
- private fun onUnloaded(clockId: ClockId) {
- logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" })
+ private fun onUnloaded(info: ClockInfo) {
+ val isCurrent = currentClockId == info.metadata.clockId
+ logger.tryLog(
+ TAG,
+ if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
+ {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ },
+ { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ )
- if (currentClockId == clockId) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = clockId },
- { "Current clock ($str1) was unloaded" }
- )
+ if (isCurrent) {
triggerOnCurrentClockChanged()
}
}
- private fun onDisconnected(clockId: ClockId) {
- logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" })
-
- if (currentClockId == clockId) {
- logger.tryLog(
- TAG,
- LogLevel.WARNING,
- { str1 = clockId },
- { "Current clock ($str1) was disconnected" }
- )
- }
+ private fun onDisconnected(info: ClockInfo) {
+ val isCurrent = currentClockId == info.metadata.clockId
+ logger.tryLog(
+ TAG,
+ if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
+ {
+ str1 = info.metadata.clockId
+ str2 = info.manager.toString()
+ bool1 = isCurrent
+ },
+ { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
+ )
}
fun getClocks(): List<ClockMetadata> {
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index 56c3f93..3e5e8a0 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -33,6 +33,12 @@
/** Returns the currently loaded plugin instance (if plugin is loaded) */
T getPlugin();
+ /** Returns true if the lifecycle manager should log debug messages */
+ boolean getIsDebug();
+
+ /** Sets whether or not hte lifecycle manager should log debug messages */
+ void setIsDebug(boolean debug);
+
/** returns true if the plugin is currently loaded */
default boolean isLoaded() {
return getPlugin() != null;
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 3194815..75de943 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -70,9 +70,6 @@
-keep class com.android.systemui.log.core.** {
*;
}
--keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
- *;
-}
-keep class androidx.core.app.CoreComponentFactory
# Keep the wm shell lib
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
new file mode 100644
index 0000000..569dd4c
--- /dev/null
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -0,0 +1,69 @@
+<!--
+ Copyright (C) 2023 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingHorizontal="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:background="@*android:drawable/bottomsheet_background"
+ android:paddingBottom="@dimen/dialog_bottom_padding">
+
+ <ImageView
+ android:id="@+id/connected_display_dialog_icon"
+ android:layout_width="@dimen/screenrecord_logo_size"
+ android:layout_height="@dimen/screenrecord_logo_size"
+ android:importantForAccessibility="no"
+ android:src="@drawable/stat_sys_connected_display"
+ android:tint="?androidprv:attr/materialColorPrimary" />
+
+ <TextView
+ android:id="@+id/connected_display_dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_title_margin_top"
+ android:gravity="center"
+ android:text="@string/connected_display_dialog_start_mirroring"
+ android:textAppearance="@style/TextAppearance.Dialog.Title" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/screenrecord_buttons_margin_top"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/cancel" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <Button
+ android:id="@+id/enable_display"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enable_display" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b37aeee..6840108 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3184,6 +3184,12 @@
<!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
<string name="install_app">Install app</string>
+ <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
+ <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
+
+ <!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
+ <string name="enable_display">Enable display</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone & Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index ca30e15..a6517c1 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
"SystemUIAnimationLib",
"SystemUIPluginLib",
"SystemUIUnfoldLib",
+ "SystemUISharedLib-Keyguard",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
"androidx.lifecycle_lifecycle-runtime-ktx",
diff --git a/packages/SystemUI/shared/keyguard/Android.bp b/packages/SystemUI/shared/keyguard/Android.bp
new file mode 100644
index 0000000..2181439
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/Android.bp
@@ -0,0 +1,16 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUISharedLib-Keyguard",
+ srcs: [
+ "src/**/*.java",
+ ],
+ min_sdk_version: "current",
+}
diff --git a/packages/SystemUI/shared/keyguard/AndroidManifest.xml b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
new file mode 100644
index 0000000..49bee08
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.shared.keyguard">
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
new file mode 100644
index 0000000..fe12134
--- /dev/null
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2023 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.keyguard;
+
+import android.annotation.CallSuper;
+import android.content.Context;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+/**
+ * A View similar to a textView which contains password text and can animate when the text is
+ * changed
+ */
+public abstract class BasePasswordTextView extends FrameLayout {
+ private String mText = "";
+ private UserActivityListener mUserActivityListener;
+ protected boolean mIsPinHinting;
+ protected PinShapeInput mPinShapeInput;
+ protected boolean mShowPassword = true;
+ protected boolean mUsePinShapes = false;
+ protected static final char DOT = '\u2022';
+
+ /** Listens to user activities like appending, deleting and resetting PIN text */
+ public interface UserActivityListener {
+
+ /** Listens to user activities. */
+ void onUserActivity();
+ }
+
+ public BasePasswordTextView(Context context) {
+ this(context, null);
+ }
+
+ public BasePasswordTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BasePasswordTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BasePasswordTextView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ protected abstract PinShapeInput inflatePinShapeInput(boolean isPinHinting);
+
+ protected abstract boolean shouldSendAccessibilityEvent();
+
+ protected void onAppend(char c, int newLength) {}
+
+ protected void onDelete(int index) {}
+
+ protected void onReset(boolean animated) {}
+
+ @CallSuper
+ protected void onUserActivity() {
+ if (mUserActivityListener != null) {
+ mUserActivityListener.onUserActivity();
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ /** Appends a PIN text */
+ public void append(char c) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = mText + c;
+ int newLength = mText.length();
+ onAppend(c, newLength);
+
+ if (mPinShapeInput != null) {
+ mPinShapeInput.append();
+ }
+
+ onUserActivity();
+
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+ }
+
+ /** Sets a listener who is notified on user activity */
+ public void setUserActivityListener(UserActivityListener userActivityListener) {
+ mUserActivityListener = userActivityListener;
+ }
+
+ /** Deletes the last PIN text */
+ public void deleteLastChar() {
+ int length = mText.length();
+ if (length > 0) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = mText.substring(0, length - 1);
+ onDelete(length - 1);
+
+ if (mPinShapeInput != null) {
+ mPinShapeInput.delete();
+ }
+
+ sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
+ }
+ onUserActivity();
+ }
+
+ /** Gets entered PIN text */
+ public String getText() {
+ return mText;
+ }
+
+ /** Gets a transformed text for accessibility event. Called before text changed. */
+ protected CharSequence getTransformedText() {
+ return String.valueOf(DOT).repeat(mText.length());
+ }
+
+ /** Gets a transformed text for accessibility event. Called after text changed. */
+ protected CharSequence getTransformedText(int fromIndex, int removedCount, int addedCount) {
+ return getTransformedText();
+ }
+
+ /** Reset PIN text without error */
+ public void reset(boolean animated, boolean announce) {
+ reset(false /* error */, animated, announce);
+ }
+
+ /** Reset PIN text */
+ public void reset(boolean error, boolean animated, boolean announce) {
+ CharSequence textbefore = getTransformedText();
+
+ mText = "";
+
+ onReset(animated);
+ if (animated) {
+ onUserActivity();
+ }
+
+ if (mPinShapeInput != null) {
+ if (error) {
+ mPinShapeInput.resetWithError();
+ } else {
+ mPinShapeInput.reset();
+ }
+ }
+
+ if (announce) {
+ sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
+ }
+ }
+
+ void sendAccessibilityEventTypeViewTextChanged(
+ CharSequence beforeText, int fromIndex, int removedCount, int addedCount) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && shouldSendAccessibilityEvent()) {
+ AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setFromIndex(fromIndex);
+ event.setRemovedCount(removedCount);
+ event.setAddedCount(addedCount);
+ event.setBeforeText(beforeText);
+ CharSequence transformedText = getTransformedText(fromIndex, removedCount, addedCount);
+ if (!TextUtils.isEmpty(transformedText)) {
+ event.getText().add(transformedText);
+ }
+ event.setPassword(true);
+ sendAccessibilityEventUnchecked(event);
+ }
+ }
+
+ /** Sets whether to use pin shapes. */
+ public void setUsePinShapes(boolean usePinShapes) {
+ mUsePinShapes = usePinShapes;
+ }
+
+ /** Determines whether AutoConfirmation feature is on. */
+ public void setIsPinHinting(boolean isPinHinting) {
+ // Do not reinflate the view if we are using the same one.
+ if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
+ return;
+ }
+ mIsPinHinting = isPinHinting;
+
+ if (mPinShapeInput != null) {
+ removeView(mPinShapeInput.getView());
+ mPinShapeInput = null;
+ }
+
+ mPinShapeInput = inflatePinShapeInput(isPinHinting);
+ addView(mPinShapeInput.getView());
+ }
+
+ /** Controls whether the last entered digit is briefly shown after being entered */
+ public void setShowPassword(boolean enabled) {
+ mShowPassword = enabled;
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+
+ event.setClassName(EditText.class.getName());
+ event.setPassword(true);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ info.setClassName(EditText.class.getName());
+ info.setPassword(true);
+ info.setText(getTransformedText());
+
+ info.setEditable(true);
+
+ info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
similarity index 85%
rename from packages/SystemUI/src/com/android/keyguard/PinShapeInput.java
rename to packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
index 52ae6ba..d2b4066 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java
+++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/PinShapeInput.java
@@ -44,6 +44,14 @@
void reset();
/**
+ * This is the method that is triggered for resetting the view with error If it doesn't have to
+ * show something regarding error, just reset
+ */
+ default void resetWithError() {
+ reset();
+ }
+
+ /**
* This is the method that is triggered for getting the view
*/
View getView();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 6b67c09..7719e95 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -55,7 +55,9 @@
private final PluginListener<T> mListener;
private final ComponentName mComponentName;
private final PluginFactory<T> mPluginFactory;
+ private final String mTag;
+ private boolean mIsDebug = false;
private Context mPluginContext;
private T mPlugin;
@@ -71,27 +73,51 @@
mComponentName = componentName;
mPluginFactory = pluginFactory;
mPlugin = plugin;
+ mTag = TAG + mComponentName.toShortString()
+ + '@' + Integer.toHexString(hashCode());
if (mPlugin != null) {
mPluginContext = mPluginFactory.createPluginContext();
}
}
+ @Override
+ public String toString() {
+ return mTag;
+ }
+
+ public boolean getIsDebug() {
+ return mIsDebug;
+ }
+
+ public void setIsDebug(boolean debug) {
+ mIsDebug = debug;
+ }
+
+ private void logDebug(String message) {
+ if (mIsDebug) {
+ Log.i(mTag, message);
+ }
+ }
+
/** Alerts listener and plugin that the plugin has been created. */
public void onCreate() {
boolean loadPlugin = mListener.onPluginAttached(this);
if (!loadPlugin) {
if (mPlugin != null) {
+ logDebug("onCreate: auto-unload");
unloadPlugin();
}
return;
}
if (mPlugin == null) {
+ logDebug("onCreate auto-load");
loadPlugin();
return;
}
+ logDebug("onCreate: load callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
@@ -103,6 +129,7 @@
/** Alerts listener and plugin that the plugin is being shutdown. */
public void onDestroy() {
+ logDebug("onDestroy");
unloadPlugin();
mListener.onPluginDetached(this);
}
@@ -118,15 +145,18 @@
*/
public void loadPlugin() {
if (mPlugin != null) {
+ logDebug("Load request when already loaded");
return;
}
mPlugin = mPluginFactory.createPlugin();
mPluginContext = mPluginFactory.createPluginContext();
if (mPlugin == null || mPluginContext == null) {
+ Log.e(mTag, "Requested load, but failed");
return;
}
+ logDebug("Loaded plugin; running callbacks");
mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
@@ -143,9 +173,11 @@
*/
public void unloadPlugin() {
if (mPlugin == null) {
+ logDebug("Unload request when already unloaded");
return;
}
+ logDebug("Unloading plugin, running callbacks");
mListener.onPluginUnloaded(mPlugin, this);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onDestroy for plugins that aren't fragments, as fragments
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 06b6692..1703b30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -48,6 +48,7 @@
private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
+ private static final float SMARTSPACE_TOP_PADDING_MULTIPLIER = 2.625f;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -96,6 +97,14 @@
private KeyguardClockFrame mLargeClockFrame;
private ClockController mClock;
+ // It's bc_smartspace_view, assigned by KeyguardClockSwitchController
+ // to get the top padding for translating smartspace for weather clock
+ private View mSmartspace;
+
+ // Smartspace in weather clock is translated by this value
+ // to compensate for the position invisible dateWeatherView
+ private int mSmartspaceTop = -1;
+
private KeyguardStatusAreaView mStatusArea;
private int mSmartspaceTopOffset;
private float mWeatherClockSmartspaceScaling = 1f;
@@ -134,8 +143,11 @@
public void onConfigChanged() {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
- mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_smartspace_top_offset);
+ mSmartspaceTopOffset = (int) (mContext.getResources().getDimensionPixelSize(
+ R.dimen.keyguard_smartspace_top_offset)
+ * mContext.getResources().getConfiguration().fontScale
+ / mContext.getResources().getDisplayMetrics().density
+ * SMARTSPACE_TOP_PADDING_MULTIPLIER);
mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
@@ -145,6 +157,12 @@
updateStatusArea(/* animate= */false);
}
+ /** Get bc_smartspace_view from KeyguardClockSwitchController
+ * Use its top to decide the translation value */
+ public void setSmartspace(View smartspace) {
+ mSmartspace = smartspace;
+ }
+
/** Sets whether the large clock is being shown on a connected display. */
public void setLargeClockOnSecondaryDisplay(boolean onSecondaryDisplay) {
if (mClock != null) {
@@ -295,7 +313,7 @@
&& mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
statusAreaClockScale = mWeatherClockSmartspaceScaling;
statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
- statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+ statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY - mSmartspaceTop;
if (mSplitShadeCentered) {
statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
}
@@ -418,10 +436,14 @@
post(() -> updateClockTargetRegions());
}
- if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+ if (mSmartspace != null && mSmartspaceTop != mSmartspace.getTop()) {
+ mSmartspaceTop = mSmartspace.getTop();
post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
}
+ if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+ post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
+ }
mChildrenAreLaidOut = true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6d2880e..d897960 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -372,6 +372,7 @@
mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);
mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
+ mView.setSmartspace(mSmartspaceView);
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 8611dbbb..1d37809 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -142,11 +142,13 @@
mLockIconCenter.y + mRadius);
final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- lp.width = (int) (mSensorRect.right - mSensorRect.left);
- lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
- lp.topMargin = (int) mSensorRect.top;
- lp.setMarginStart((int) mSensorRect.left);
- setLayoutParams(lp);
+ if (lp != null) {
+ lp.width = (int) (mSensorRect.right - mSensorRect.left);
+ lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
+ lp.topMargin = (int) mSensorRect.top;
+ lp.setMarginStart((int) mSensorRect.left);
+ setLayoutParams(lp);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 951a6ae..ab9b647 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -28,6 +28,7 @@
import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
@@ -74,7 +75,6 @@
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.PrintWriter;
@@ -90,7 +90,7 @@
* icon will show a set distance from the bottom of the device.
*/
@SysUISingleton
-public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
+public class LockIconViewController implements Dumpable {
private static final String TAG = "LockIconViewController";
private static final float sDefaultDensity =
(float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
@@ -109,6 +109,8 @@
@NonNull private final ConfigurationController mConfigurationController;
@NonNull private final DelayableExecutor mExecutor;
private boolean mUdfpsEnrolled;
+ private Resources mResources;
+ private Context mContext;
@NonNull private final AnimatedStateListDrawable mIcon;
@@ -120,6 +122,7 @@
@NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
@NonNull private final KeyguardInteractor mKeyguardInteractor;
+ @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -154,6 +157,7 @@
private boolean mDownDetected;
private final Rect mSensorTouchLocation = new Rect();
+ private LockIconView mView;
@VisibleForTesting
final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
@@ -178,7 +182,6 @@
@Inject
public LockIconViewController(
- @Nullable LockIconView view,
@NonNull StatusBarStateController statusBarStateController,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull KeyguardViewController keyguardViewController,
@@ -195,9 +198,9 @@
@NonNull KeyguardTransitionInteractor transitionInteractor,
@NonNull KeyguardInteractor keyguardInteractor,
@NonNull FeatureFlags featureFlags,
- PrimaryBouncerInteractor primaryBouncerInteractor
+ PrimaryBouncerInteractor primaryBouncerInteractor,
+ Context context
) {
- super(view);
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mAuthController = authController;
@@ -218,16 +221,40 @@
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
mIcon = (AnimatedStateListDrawable)
- resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme());
- mView.setImageDrawable(mIcon);
+ resources.getDrawable(R.drawable.super_lock_icon, context.getTheme());
mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
dumpManager.registerDumpable(TAG, this);
+ mResources = resources;
+ mContext = context;
+
+ mAccessibilityDelegate = new View.AccessibilityDelegate() {
+ private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
+ new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
+ mResources.getString(R.string.accessibility_authenticate_hint));
+ private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint =
+ new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfoCompat.ACTION_CLICK,
+ mResources.getString(R.string.accessibility_enter_hint));
+ public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(v, info);
+ if (isActionable()) {
+ if (mShowLockIcon) {
+ info.addAction(mAccessibilityAuthenticateHint);
+ } else if (mShowUnlockIcon) {
+ info.addAction(mAccessibilityEnterHint);
+ }
+ }
+ }
+ };
}
- @Override
- protected void onInit() {
+ /** Sets the LockIconView to the controller and rebinds any that depend on it. */
+ public void setLockIconView(LockIconView lockIconView) {
+ mView = lockIconView;
+ mView.setImageDrawable(mIcon);
mView.setAccessibilityDelegate(mAccessibilityDelegate);
if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
@@ -240,10 +267,7 @@
collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(),
mIsActiveDreamLockscreenHostedCallback);
}
- }
- @Override
- protected void onViewAttached() {
updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
@@ -256,19 +280,49 @@
mStatusBarState = mStatusBarStateController.getState();
updateColors();
- mConfigurationController.addCallback(mConfigurationListener);
-
- mAuthController.addCallback(mAuthControllerCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.addCallback(mStatusBarStateListener);
- mKeyguardStateController.addCallback(mKeyguardStateCallback);
mDownDetected = false;
updateBurnInOffsets();
updateVisibility();
+ updateAccessibility();
+
+ lockIconView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ registerCallbacks();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ unregisterCallbacks();
+ }
+ });
+
+ if (lockIconView.isAttachedToWindow()) {
+ registerCallbacks();
+ }
+ }
+
+ private void registerCallbacks() {
+ mConfigurationController.addCallback(mConfigurationListener);
+ mAuthController.addCallback(mAuthControllerCallback);
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
- updateAccessibility();
+
+ }
+
+ private void unregisterCallbacks() {
+ mAuthController.removeCallback(mAuthControllerCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
+ mAccessibilityManager.removeAccessibilityStateChangeListener(
+ mAccessibilityStateChangeListener);
+
}
private void updateAccessibility() {
@@ -279,18 +333,6 @@
}
}
- @Override
- protected void onViewDetached() {
- mAuthController.removeCallback(mAuthControllerCallback);
- mConfigurationController.removeCallback(mConfigurationListener);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
- mKeyguardStateController.removeCallback(mKeyguardStateCallback);
-
- mAccessibilityManager.removeAccessibilityStateChangeListener(
- mAccessibilityStateChangeListener);
- }
-
public float getTop() {
return mView.getLocationTop();
}
@@ -363,28 +405,6 @@
}
}
- private final View.AccessibilityDelegate mAccessibilityDelegate =
- new View.AccessibilityDelegate() {
- private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
- new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_CLICK,
- getResources().getString(R.string.accessibility_authenticate_hint));
- private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint =
- new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfoCompat.ACTION_CLICK,
- getResources().getString(R.string.accessibility_enter_hint));
- public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(v, info);
- if (isActionable()) {
- if (mShowLockIcon) {
- info.addAction(mAccessibilityAuthenticateHint);
- } else if (mShowUnlockIcon) {
- info.addAction(mAccessibilityEnterHint);
- }
- }
- }
- };
-
private boolean isLockScreen() {
return !mIsDozing
&& !mIsBouncerShowing
@@ -401,18 +421,15 @@
}
private void updateConfiguration() {
- WindowManager windowManager = getContext().getSystemService(WindowManager.class);
+ WindowManager windowManager = mContext.getSystemService(WindowManager.class);
Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
mWidthPixels = bounds.right;
mHeightPixels = bounds.bottom;
- mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
- mDefaultPaddingPx =
- getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
-
- mUnlockedLabel = mView.getContext().getResources().getString(
+ mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
+ mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding);
+ mUnlockedLabel = mResources.getString(
R.string.accessibility_unlock_button);
- mLockedLabel = mView.getContext()
- .getResources().getString(R.string.accessibility_lock_icon);
+ mLockedLabel = mResources.getString(R.string.accessibility_lock_icon);
updateLockIconLocation();
}
@@ -755,7 +772,7 @@
} else {
mVibrator.vibrate(
Process.myUid(),
- getContext().getOpPackageName(),
+ mContext.getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-icon-down",
TOUCH_VIBRATION_ATTRIBUTES);
@@ -769,7 +786,7 @@
} else {
mVibrator.vibrate(
Process.myUid(),
- getContext().getOpPackageName(),
+ mContext.getOpPackageName(),
UdfpsController.EFFECT_CLICK,
"lock-screen-lock-icon-longpress",
TOUCH_VIBRATION_ATTRIBUTES);
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 8e8ee48..9a2ffe0 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.keyguard;
@@ -30,18 +30,11 @@
import android.graphics.Typeface;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.text.InputType;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
-import android.widget.EditText;
-import android.widget.FrameLayout;
import com.android.settingslib.Utils;
import com.android.systemui.R;
@@ -52,12 +45,11 @@
* A View similar to a textView which contains password text and can animate when the text is
* changed
*/
-public class PasswordTextView extends FrameLayout {
-
- private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
- private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
+public class PasswordTextView extends BasePasswordTextView {
public static final long APPEAR_DURATION = 160;
public static final long DISAPPEAR_DURATION = 160;
+ private static final float DOT_OVERSHOOT_FACTOR = 1.5f;
+ private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320;
private static final long RESET_DELAY_PER_ELEMENT = 40;
private static final long RESET_MAX_DELAY = 200;
@@ -82,15 +74,12 @@
*/
private static final float OVERSHOOT_TIME_POSITION = 0.5f;
- private static char DOT = '\u2022';
-
/**
* The raw text size, will be multiplied by the scaled density when drawn
*/
private int mTextHeightRaw;
private final int mGravity;
private ArrayList<CharState> mTextChars = new ArrayList<>();
- private String mText = "";
private int mDotSize;
private PowerManager mPM;
private int mCharPadding;
@@ -99,15 +88,6 @@
private Interpolator mAppearInterpolator;
private Interpolator mDisappearInterpolator;
private Interpolator mFastOutSlowInInterpolator;
- private boolean mShowPassword = true;
- private UserActivityListener mUserActivityListener;
- private boolean mIsPinHinting;
- private PinShapeInput mPinShapeInput;
- private boolean mUsePinShapes = false;
-
- public interface UserActivityListener {
- void onUserActivity();
- }
public PasswordTextView(Context context) {
this(context, null);
@@ -145,8 +125,7 @@
mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding,
getContext().getResources().getDimensionPixelSize(
R.dimen.password_char_padding));
- mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor,
- Color.WHITE);
+ mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor, Color.WHITE);
mDrawPaint.setColor(mDrawColor);
} finally {
@@ -156,8 +135,7 @@
mDrawPaint.setFlags(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mDrawPaint.setTextAlign(Paint.Align.CENTER);
mDrawPaint.setTypeface(Typeface.create(
- context.getString(com.android.internal.R.string.config_headlineFontFamily),
- 0));
+ context.getString(com.android.internal.R.string.config_headlineFontFamily), 0));
mAppearInterpolator = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext,
@@ -169,9 +147,19 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- mTextHeightRaw = getContext().getResources().getInteger(
- R.integer.scaled_password_text_size);
+ protected PinShapeInput inflatePinShapeInput(boolean isPinHinting) {
+ if (isPinHinting) {
+ return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_hinting_view, null);
+ } else {
+ return (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_non_hinting_view, null);
+ }
+ }
+
+ @Override
+ protected boolean shouldSendAccessibilityEvent() {
+ return isFocused() || isSelected() && isShown();
}
@Override
@@ -201,8 +189,8 @@
int charHeight = (bounds.bottom - bounds.top);
float yPosition =
(getHeight() - getPaddingBottom() - getPaddingTop()) / 2 + getPaddingTop();
- canvas.clipRect(getPaddingLeft(), getPaddingTop(),
- getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
+ canvas.clipRect(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+ getHeight() - getPaddingBottom());
float charLength = bounds.right - bounds.left;
for (int i = 0; i < length; i++) {
CharState charState = mTextChars.get(i);
@@ -212,6 +200,67 @@
}
}
+ @Override
+ protected void onAppend(char c, int newLength) {
+ int visibleChars = mTextChars.size();
+ CharState charState;
+ if (newLength > visibleChars) {
+ charState = obtainCharState(c);
+ mTextChars.add(charState);
+ } else {
+ charState = mTextChars.get(newLength - 1);
+ charState.whichChar = c;
+ }
+ charState.startAppearAnimation();
+
+ // ensure that the previous element is being swapped
+ if (newLength > 1) {
+ CharState previousState = mTextChars.get(newLength - 2);
+ if (previousState.isDotSwapPending) {
+ previousState.swapToDotWhenAppearFinished();
+ }
+ }
+ }
+
+ @Override
+ protected void onDelete(int index) {
+ CharState charState = mTextChars.get(index);
+ charState.startRemoveAnimation(0, 0);
+ }
+
+ @Override
+ protected void onReset(boolean animated) {
+ if (animated) {
+ int length = mTextChars.size();
+ int middleIndex = (length - 1) / 2;
+ long delayPerElement = RESET_DELAY_PER_ELEMENT;
+ for (int i = 0; i < length; i++) {
+ CharState charState = mTextChars.get(i);
+ int delayIndex;
+ if (i <= middleIndex) {
+ delayIndex = i * 2;
+ } else {
+ int distToMiddle = i - middleIndex;
+ delayIndex = (length - 1) - (distToMiddle - 1) * 2;
+ }
+ long startDelay = delayIndex * delayPerElement;
+ startDelay = Math.min(startDelay, RESET_MAX_DELAY);
+ long maxDelay = delayPerElement * (length - 1);
+ maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
+ charState.startRemoveAnimation(startDelay, maxDelay);
+ charState.removeDotSwapCallbacks();
+ }
+ } else {
+ mTextChars.clear();
+ }
+ }
+
+ @Override
+ protected void onUserActivity() {
+ mPM.userActivity(SystemClock.uptimeMillis(), false);
+ super.onUserActivity();
+ }
+
/**
* Reload colors from resources.
**/
@@ -225,8 +274,9 @@
}
@Override
- public boolean hasOverlappingRendering() {
- return false;
+ protected void onConfigurationChanged(Configuration newConfig) {
+ mTextHeightRaw = getContext().getResources().getInteger(
+ R.integer.scaled_password_text_size);
}
private Rect getCharBounds() {
@@ -252,67 +302,14 @@
return width;
}
-
- public void append(char c) {
- int visibleChars = mTextChars.size();
- CharSequence textbefore = getTransformedText();
- mText = mText + c;
- int newLength = mText.length();
- CharState charState;
- if (newLength > visibleChars) {
- charState = obtainCharState(c);
- mTextChars.add(charState);
- } else {
- charState = mTextChars.get(newLength - 1);
- charState.whichChar = c;
- }
- if (mPinShapeInput != null) {
- mPinShapeInput.append();
- }
- charState.startAppearAnimation();
-
- // ensure that the previous element is being swapped
- if (newLength > 1) {
- CharState previousState = mTextChars.get(newLength - 2);
- if (previousState.isDotSwapPending) {
- previousState.swapToDotWhenAppearFinished();
- }
- }
- userActivity();
- sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
+ private CharState obtainCharState(char c) {
+ CharState charState = new CharState();
+ charState.whichChar = c;
+ return charState;
}
- public void setUserActivityListener(UserActivityListener userActivityListener) {
- mUserActivityListener = userActivityListener;
- }
-
- private void userActivity() {
- mPM.userActivity(SystemClock.uptimeMillis(), false);
- if (mUserActivityListener != null) {
- mUserActivityListener.onUserActivity();
- }
- }
-
- public void deleteLastChar() {
- int length = mText.length();
- CharSequence textbefore = getTransformedText();
- if (length > 0) {
- mText = mText.substring(0, length - 1);
- CharState charState = mTextChars.get(length - 1);
- charState.startRemoveAnimation(0, 0);
- sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0);
- if (mPinShapeInput != null) {
- mPinShapeInput.delete();
- }
- }
- userActivity();
- }
-
- public String getText() {
- return mText;
- }
-
- private CharSequence getTransformedText() {
+ @Override
+ protected CharSequence getTransformedText() {
int textLength = mTextChars.size();
StringBuilder stringBuilder = new StringBuilder(textLength);
for (int i = 0; i < textLength; i++) {
@@ -327,130 +324,6 @@
return stringBuilder;
}
- private CharState obtainCharState(char c) {
- CharState charState = new CharState();
- charState.whichChar = c;
- return charState;
- }
-
- public void reset(boolean animated, boolean announce) {
- CharSequence textbefore = getTransformedText();
- mText = "";
- int length = mTextChars.size();
- int middleIndex = (length - 1) / 2;
- long delayPerElement = RESET_DELAY_PER_ELEMENT;
- for (int i = 0; i < length; i++) {
- CharState charState = mTextChars.get(i);
- if (animated) {
- int delayIndex;
- if (i <= middleIndex) {
- delayIndex = i * 2;
- } else {
- int distToMiddle = i - middleIndex;
- delayIndex = (length - 1) - (distToMiddle - 1) * 2;
- }
- long startDelay = delayIndex * delayPerElement;
- startDelay = Math.min(startDelay, RESET_MAX_DELAY);
- long maxDelay = delayPerElement * (length - 1);
- maxDelay = Math.min(maxDelay, RESET_MAX_DELAY) + DISAPPEAR_DURATION;
- charState.startRemoveAnimation(startDelay, maxDelay);
- charState.removeDotSwapCallbacks();
- }
- }
- if (!animated) {
- mTextChars.clear();
- } else {
- userActivity();
- }
- if (mPinShapeInput != null) {
- mPinShapeInput.reset();
- }
- if (announce) {
- sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
- }
- }
-
- void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex,
- int removedCount, int addedCount) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
- AccessibilityEvent event =
- AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
- event.setFromIndex(fromIndex);
- event.setRemovedCount(removedCount);
- event.setAddedCount(addedCount);
- event.setBeforeText(beforeText);
- CharSequence transformedText = getTransformedText();
- if (!TextUtils.isEmpty(transformedText)) {
- event.getText().add(transformedText);
- }
- event.setPassword(true);
- sendAccessibilityEventUnchecked(event);
- }
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
-
- event.setClassName(EditText.class.getName());
- event.setPassword(true);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.setClassName(EditText.class.getName());
- info.setPassword(true);
- info.setText(getTransformedText());
-
- info.setEditable(true);
-
- info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD);
- }
-
- /**
- * Sets whether to use pin shapes.
- */
- public void setUsePinShapes(boolean usePinShapes) {
- mUsePinShapes = usePinShapes;
- }
-
- /**
- * Determines whether AutoConfirmation feature is on.
- *
- * @param isPinHinting
- */
- public void setIsPinHinting(boolean isPinHinting) {
- // Do not reinflate the view if we are using the same one.
- if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
- return;
- }
- mIsPinHinting = isPinHinting;
-
- if (mPinShapeInput != null) {
- removeView(mPinShapeInput.getView());
- mPinShapeInput = null;
- }
-
- if (isPinHinting) {
- mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_pin_shape_hinting_view, null);
- } else {
- mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
- R.layout.keyguard_pin_shape_non_hinting_view, null);
- }
- addView(mPinShapeInput.getView());
- }
-
- /**
- * Controls whether the last entered digit is briefly shown after being entered
- */
- public void setShowPassword(boolean enabled) {
- mShowPassword = enabled;
- }
-
private class CharState {
char whichChar;
ValueAnimator textAnimator;
@@ -468,6 +341,7 @@
Animator.AnimatorListener removeEndListener = new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
@@ -516,53 +390,53 @@
}
};
- private ValueAnimator.AnimatorUpdateListener dotSizeUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentDotSizeFactor = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
-
- private ValueAnimator.AnimatorUpdateListener textSizeUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- boolean textVisibleBefore = isCharVisibleForA11y();
- float beforeTextSizeFactor = currentTextSizeFactor;
- currentTextSizeFactor = (float) animation.getAnimatedValue();
- if (textVisibleBefore != isCharVisibleForA11y()) {
- currentTextSizeFactor = beforeTextSizeFactor;
- CharSequence beforeText = getTransformedText();
- currentTextSizeFactor = (float) animation.getAnimatedValue();
- int indexOfThisChar = mTextChars.indexOf(CharState.this);
- if (indexOfThisChar >= 0) {
- sendAccessibilityEventTypeViewTextChanged(
- beforeText, indexOfThisChar, 1, 1);
+ private ValueAnimator.AnimatorUpdateListener mDotSizeUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentDotSizeFactor = (float) animation.getAnimatedValue();
+ invalidate();
}
- }
- invalidate();
- }
- };
+ };
- private ValueAnimator.AnimatorUpdateListener textTranslationUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentTextTranslationY = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
+ private ValueAnimator.AnimatorUpdateListener mTextSizeUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ boolean textVisibleBefore = isCharVisibleForA11y();
+ float beforeTextSizeFactor = currentTextSizeFactor;
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ if (textVisibleBefore != isCharVisibleForA11y()) {
+ currentTextSizeFactor = beforeTextSizeFactor;
+ CharSequence beforeText = getTransformedText();
+ currentTextSizeFactor = (float) animation.getAnimatedValue();
+ int indexOfThisChar = mTextChars.indexOf(CharState.this);
+ if (indexOfThisChar >= 0) {
+ sendAccessibilityEventTypeViewTextChanged(beforeText,
+ indexOfThisChar, 1, 1);
+ }
+ }
+ invalidate();
+ }
+ };
- private ValueAnimator.AnimatorUpdateListener widthUpdater
- = new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- currentWidthFactor = (float) animation.getAnimatedValue();
- invalidate();
- }
- };
+ private ValueAnimator.AnimatorUpdateListener mTextTranslationUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentTextTranslationY = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
+
+ private ValueAnimator.AnimatorUpdateListener mWidthUpdater =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ currentWidthFactor = (float) animation.getAnimatedValue();
+ invalidate();
+ }
+ };
private Runnable dotSwapperRunnable = new Runnable() {
@Override
@@ -573,12 +447,15 @@
};
void startRemoveAnimation(long startDelay, long widthDelay) {
- boolean dotNeedsAnimation = (currentDotSizeFactor > 0.0f && dotAnimator == null)
- || (dotAnimator != null && dotAnimationIsGrowing);
- boolean textNeedsAnimation = (currentTextSizeFactor > 0.0f && textAnimator == null)
- || (textAnimator != null && textAnimationIsGrowing);
- boolean widthNeedsAnimation = (currentWidthFactor > 0.0f && widthAnimator == null)
- || (widthAnimator != null && widthAnimationIsGrowing);
+ boolean dotNeedsAnimation =
+ (currentDotSizeFactor > 0.0f && dotAnimator == null) || (dotAnimator != null
+ && dotAnimationIsGrowing);
+ boolean textNeedsAnimation =
+ (currentTextSizeFactor > 0.0f && textAnimator == null) || (textAnimator != null
+ && textAnimationIsGrowing);
+ boolean widthNeedsAnimation =
+ (currentWidthFactor > 0.0f && widthAnimator == null) || (widthAnimator != null
+ && widthAnimationIsGrowing);
if (dotNeedsAnimation) {
startDotDisappearAnimation(startDelay);
}
@@ -591,10 +468,10 @@
}
void startAppearAnimation() {
- boolean dotNeedsAnimation = !mShowPassword
- && (dotAnimator == null || !dotAnimationIsGrowing);
- boolean textNeedsAnimation = mShowPassword
- && (textAnimator == null || !textAnimationIsGrowing);
+ boolean dotNeedsAnimation =
+ !mShowPassword && (dotAnimator == null || !dotAnimationIsGrowing);
+ boolean textNeedsAnimation =
+ mShowPassword && (textAnimator == null || !textAnimationIsGrowing);
boolean widthNeedsAnimation = (widthAnimator == null || !widthAnimationIsGrowing);
if (dotNeedsAnimation) {
startDotAppearAnimation(0);
@@ -628,8 +505,8 @@
void swapToDotWhenAppearFinished() {
removeDotSwapCallbacks();
if (textAnimator != null) {
- long remainingDuration = textAnimator.getDuration()
- - textAnimator.getCurrentPlayTime();
+ long remainingDuration =
+ textAnimator.getDuration() - textAnimator.getCurrentPlayTime();
postDotSwap(remainingDuration + TEXT_REST_DURATION_AFTER_APPEAR);
} else {
performSwap();
@@ -638,14 +515,14 @@
private void performSwap() {
startTextDisappearAnimation(0);
- startDotAppearAnimation(DISAPPEAR_DURATION
- - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
+ startDotAppearAnimation(
+ DISAPPEAR_DURATION - DOT_APPEAR_TEXT_DISAPPEAR_OVERLAP_DURATION);
}
private void startWidthDisappearAnimation(long widthDelay) {
cancelAnimator(widthAnimator);
widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 0.0f);
- widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addUpdateListener(mWidthUpdater);
widthAnimator.addListener(widthFinishListener);
widthAnimator.addListener(removeEndListener);
widthAnimator.setDuration((long) (DISAPPEAR_DURATION * currentWidthFactor));
@@ -657,7 +534,7 @@
private void startTextDisappearAnimation(long startDelay) {
cancelAnimator(textAnimator);
textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 0.0f);
- textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addUpdateListener(mTextSizeUpdater);
textAnimator.addListener(textFinishListener);
textAnimator.setInterpolator(mDisappearInterpolator);
textAnimator.setDuration((long) (DISAPPEAR_DURATION * currentTextSizeFactor));
@@ -669,7 +546,7 @@
private void startDotDisappearAnimation(long startDelay) {
cancelAnimator(dotAnimator);
ValueAnimator animator = ValueAnimator.ofFloat(currentDotSizeFactor, 0.0f);
- animator.addUpdateListener(dotSizeUpdater);
+ animator.addUpdateListener(mDotSizeUpdater);
animator.addListener(dotFinishListener);
animator.setInterpolator(mDisappearInterpolator);
long duration = (long) (DISAPPEAR_DURATION * Math.min(currentDotSizeFactor, 1.0f));
@@ -683,7 +560,7 @@
private void startWidthAppearAnimation() {
cancelAnimator(widthAnimator);
widthAnimator = ValueAnimator.ofFloat(currentWidthFactor, 1.0f);
- widthAnimator.addUpdateListener(widthUpdater);
+ widthAnimator.addUpdateListener(mWidthUpdater);
widthAnimator.addListener(widthFinishListener);
widthAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentWidthFactor)));
widthAnimator.start();
@@ -693,7 +570,7 @@
private void startTextAppearAnimation() {
cancelAnimator(textAnimator);
textAnimator = ValueAnimator.ofFloat(currentTextSizeFactor, 1.0f);
- textAnimator.addUpdateListener(textSizeUpdater);
+ textAnimator.addUpdateListener(mTextSizeUpdater);
textAnimator.addListener(textFinishListener);
textAnimator.setInterpolator(mAppearInterpolator);
textAnimator.setDuration((long) (APPEAR_DURATION * (1f - currentTextSizeFactor)));
@@ -703,7 +580,7 @@
// handle translation
if (textTranslateAnimator == null) {
textTranslateAnimator = ValueAnimator.ofFloat(1.0f, 0.0f);
- textTranslateAnimator.addUpdateListener(textTranslationUpdater);
+ textTranslateAnimator.addUpdateListener(mTextTranslationUpdater);
textTranslateAnimator.addListener(textTranslateFinishListener);
textTranslateAnimator.setInterpolator(mAppearInterpolator);
textTranslateAnimator.setDuration(APPEAR_DURATION);
@@ -717,14 +594,14 @@
// We perform an overshoot animation
ValueAnimator overShootAnimator = ValueAnimator.ofFloat(currentDotSizeFactor,
DOT_OVERSHOOT_FACTOR);
- overShootAnimator.addUpdateListener(dotSizeUpdater);
+ overShootAnimator.addUpdateListener(mDotSizeUpdater);
overShootAnimator.setInterpolator(mAppearInterpolator);
- long overShootDuration = (long) (DOT_APPEAR_DURATION_OVERSHOOT
- * OVERSHOOT_TIME_POSITION);
+ long overShootDuration =
+ (long) (DOT_APPEAR_DURATION_OVERSHOOT * OVERSHOOT_TIME_POSITION);
overShootAnimator.setDuration(overShootDuration);
ValueAnimator settleBackAnimator = ValueAnimator.ofFloat(DOT_OVERSHOOT_FACTOR,
1.0f);
- settleBackAnimator.addUpdateListener(dotSizeUpdater);
+ settleBackAnimator.addUpdateListener(mDotSizeUpdater);
settleBackAnimator.setDuration(DOT_APPEAR_DURATION_OVERSHOOT - overShootDuration);
settleBackAnimator.addListener(dotFinishListener);
AnimatorSet animatorSet = new AnimatorSet();
@@ -734,7 +611,7 @@
dotAnimator = animatorSet;
} else {
ValueAnimator growAnimator = ValueAnimator.ofFloat(currentDotSizeFactor, 1.0f);
- growAnimator.addUpdateListener(dotSizeUpdater);
+ growAnimator.addUpdateListener(mDotSizeUpdater);
growAnimator.setDuration((long) (APPEAR_DURATION * (1.0f - currentDotSizeFactor)));
growAnimator.addListener(dotFinishListener);
growAnimator.setStartDelay(delay);
diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
index 56c0953..5f33ef9 100644
--- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -61,7 +61,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- if (getChildCount() > 0) {
+ if (getChildCount() > 2) {
View firstChild = getChildAt(0);
boolean isVisible = firstChild.getLocalVisibleRect(mFirstChildVisibleRect);
boolean clipped = mFirstChildVisibleRect.left > 0
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
index 8dbe5e0..d59f51f 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerScope.java
@@ -24,7 +24,7 @@
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardBouncerComponent}.
*/
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
index f498ef3..394d63171 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewScope.java
@@ -24,7 +24,7 @@
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardStatusBarViewComponent}.
*/
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
index aeae8e3..6c2c9f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewScope.java
@@ -24,7 +24,7 @@
import javax.inject.Scope;
/**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
+ * Scope annotation for singleton items within the {@link KeyguardStatusViewComponent}.
*/
@Documented
@Retention(RUNTIME)
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index eec16e6..c9801d7 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -52,6 +52,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
@@ -184,6 +185,10 @@
protected void setListening(boolean listening) {
mListening = listening;
if (listening) {
+ // System UI could be restarted while ops are active, so fetch the currently active ops
+ // once System UI starts listening again.
+ fetchCurrentActiveOps();
+
mAppOps.startWatchingActive(OPS, this);
mAppOps.startWatchingNoted(OPS, this);
mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
@@ -216,6 +221,29 @@
}
}
+ private void fetchCurrentActiveOps() {
+ List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
+ for (AppOpsManager.PackageOps op : packageOps) {
+ for (AppOpsManager.OpEntry entry : op.getOps()) {
+ for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
+ entry.getAttributedOpEntries().entrySet()) {
+ if (attributedOpEntry.getValue().isRunning()) {
+ onOpActiveChanged(
+ entry.getOpStr(),
+ op.getUid(),
+ op.getPackageName(),
+ /* attributionTag= */ attributedOpEntry.getKey(),
+ /* active= */ true,
+ // AppOpsManager doesn't have a way to fetch attribution flags or
+ // chain ID given an op entry, so default them to none.
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+ AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ }
+ }
+ }
+ }
+ }
+
/**
* Adds a callback that will get notifified when an AppOp of the type the controller tracks
* changes
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 3c74bf4..066cba23 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -16,24 +16,69 @@
package com.android.systemui.back.domain.interactor
+import android.window.BackEvent
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Handles requests to go back either from a button or gesture. */
@SysUISingleton
class BackActionInteractor
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
private val statusBarStateController: StatusBarStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val shadeController: ShadeController
-) {
+ private val shadeController: ShadeController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
+ featureFlags: FeatureFlags,
+) : CoreStartable {
+
+ private var isCallbackRegistered = false
+
+ private val callback =
+ if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
+ /**
+ * New callback that handles back gesture invoked, cancel, progress and provides
+ * feedback via Shade animation.
+ */
+ object : OnBackAnimationCallback {
+ override fun onBackInvoked() {
+ onBackRequested()
+ }
+
+ override fun onBackProgressed(backEvent: BackEvent) {
+ if (shouldBackBeHandled() && shadeViewController.canBeCollapsed()) {
+ shadeViewController.onBackProgressed(backEvent.progress)
+ }
+ }
+ }
+ } else {
+ OnBackInvokedCallback { onBackRequested() }
+ }
+
+ private val onBackInvokedDispatcher: WindowOnBackInvokedDispatcher?
+ get() =
+ notificationShadeWindowController.windowRootView?.viewRootImpl?.onBackInvokedDispatcher
+
private lateinit var shadeViewController: ShadeViewController
private lateinit var qsController: QuickSettingsController
@@ -42,6 +87,19 @@
this.shadeViewController = svController
}
+ override fun start() {
+ scope.launch {
+ windowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive.collect {
+ visible ->
+ if (visible) {
+ registerBackCallback()
+ } else {
+ unregisterBackCallback()
+ }
+ }
+ }
+ }
+
fun shouldBackBeHandled(): Boolean {
return statusBarStateController.state != StatusBarState.KEYGUARD &&
statusBarStateController.state != StatusBarState.SHADE_LOCKED &&
@@ -74,4 +132,24 @@
}
return false
}
+
+ private fun registerBackCallback() {
+ if (isCallbackRegistered) {
+ return
+ }
+ onBackInvokedDispatcher?.let {
+ it.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback)
+ isCallbackRegistered = true
+ }
+ }
+
+ private fun unregisterBackCallback() {
+ if (!isCallbackRegistered) {
+ return
+ }
+ onBackInvokedDispatcher?.let {
+ it.unregisterOnBackInvokedCallback(callback)
+ isCallbackRegistered = false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e8b8f54..be08932 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -78,17 +78,6 @@
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
- /**
- * If the API caller or the user's personal preferences require explicit confirmation after
- * successful authentication.
- */
- val isConfirmationRequired: Flow<Boolean> =
- combine(_isOverlayTouched, promptSelectorInteractor.isConfirmationRequired) {
- isOverlayTouched,
- isConfirmationRequired ->
- !isOverlayTouched && isConfirmationRequired
- }
-
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind
@@ -137,6 +126,15 @@
}
.distinctUntilChanged()
+ /**
+ * If the API caller or the user's personal preferences require explicit confirmation after
+ * successful authentication. Confirmation always required when in explicit flow.
+ */
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, size) { isOverlayTouched, size ->
+ !isOverlayTouched && size.isNotSmall
+ }
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -170,12 +168,7 @@
.distinctUntilChanged()
/** If the icon can be used as a confirmation button. */
- val isIconConfirmButton: Flow<Boolean> =
- combine(size, promptSelectorInteractor.isConfirmationRequired) {
- size,
- isConfirmationRequired ->
- size.isNotSmall && isConfirmationRequired
- }
+ val isIconConfirmButton: Flow<Boolean> = size.map { it.isNotSmall }.distinctUntilChanged()
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 1bf3a9e..fc32f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.classifier.FalsingClassifier
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
@@ -50,6 +52,7 @@
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
featureFlags: FeatureFlags,
+ private val falsingInteractor: FalsingInteractor,
) {
/** The user-facing message to show in the bouncer. */
@@ -103,6 +106,34 @@
}
}
+ /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
+ fun onDown() {
+ falsingInteractor.avoidGesture()
+ }
+
+ /**
+ * Notifies of "intentional" (i.e. non-false) user interaction with the UI which is very likely
+ * to be real user interaction with the bouncer and not the result of a false touch in the
+ * user's pocket or by the user's face while holding their device up to their ear.
+ */
+ fun onIntentionalUserInput() {
+ falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
+ }
+
+ /**
+ * Notifies of false input which is very likely to be the result of a false touch in the user's
+ * pocket or by the user's face while holding their device up to their ear.
+ */
+ fun onFalseUserInput() {
+ falsingInteractor.updateFalseConfidence(
+ FalsingClassifier.Result.falsed(
+ /* confidence= */ 0.7,
+ /* context= */ javaClass.simpleName,
+ /* reason= */ "empty pattern input",
+ )
+ )
+ }
+
/**
* Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
*
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index ca15f4e..80a41ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -34,6 +34,7 @@
) {
private val _password = MutableStateFlow("")
+
/** The password entered so far. */
val password: StateFlow<String> = _password.asStateFlow()
@@ -48,6 +49,10 @@
interactor.clearMessage()
}
+ if (password.isNotEmpty()) {
+ interactor.onIntentionalUserInput()
+ }
+
_password.value = password
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 4425f9f..85eaf0b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -46,10 +46,12 @@
/** The number of columns in the dot grid. */
val columnCount = 3
+
/** The number of rows in the dot grid. */
val rowCount = 3
private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+
/** The dots that were selected by the user, in the order of selection. */
val selectedDots: StateFlow<List<PatternDotViewModel>> =
_selectedDots
@@ -61,10 +63,12 @@
)
private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
+
/** The most-recently selected dot that the user selected. */
val currentDot: StateFlow<PatternDotViewModel?> = _currentDot.asStateFlow()
private val _dots = MutableStateFlow(defaultDots())
+
/** All dots on the grid. */
val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
@@ -76,6 +80,11 @@
interactor.resetMessage()
}
+ /** Notifies that the user has placed down a pointer, not necessarily dragging just yet. */
+ fun onDown() {
+ interactor.onDown()
+ }
+
/** Notifies that the user has started a drag gesture across the dot grid. */
fun onDragStart() {
interactor.clearMessage()
@@ -124,11 +133,13 @@
dot =
PatternDotViewModel(
x =
- if (hitDot.x > dot.x) dot.x + 1
- else if (hitDot.x < dot.x) dot.x - 1 else dot.x,
+ if (hitDot.x > dot.x) {
+ dot.x + 1
+ } else if (hitDot.x < dot.x) dot.x - 1 else dot.x,
y =
- if (hitDot.y > dot.y) dot.y + 1
- else if (hitDot.y < dot.y) dot.y - 1 else dot.y,
+ if (hitDot.y > dot.y) {
+ dot.y + 1
+ } else if (hitDot.y < dot.y) dot.y - 1 else dot.y,
)
}
}
@@ -148,6 +159,12 @@
/** Notifies that the user has ended the drag gesture across the dot grid. */
fun onDragEnd() {
val pattern = _selectedDots.value.map { it.toCoordinate() }
+
+ if (pattern.size == 1) {
+ // Single dot patterns are treated as erroneous/false taps:
+ interactor.onFalseUserInput()
+ }
+
_dots.value = defaultDots()
_currentDot.value = null
_selectedDots.value = linkedSetOf()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 844cf02..ebf939b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -88,6 +88,11 @@
interactor.resetMessage()
}
+ /** Notifies that the user has placed down a pointer. */
+ fun onDown() {
+ interactor.onDown()
+ }
+
/** Notifies that the user clicked on a PIN button with the given digit value. */
fun onPinButtonClicked(input: Int) {
val pinInput = mutablePinInput.value
@@ -95,6 +100,8 @@
interactor.clearMessage()
}
+ interactor.onIntentionalUserInput()
+
mutablePinInput.value = pinInput.append(input)
tryAuthenticate(useAutoConfirm = true)
}
@@ -148,8 +155,10 @@
enum class ActionButtonAppearance {
/** Button must not be shown. */
Hidden,
+
/** Button is shown, but with no background to make it less prominent. */
Subtle,
+
/** Button is shown. */
Shown,
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index 4227a7a..b268095 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -29,12 +29,14 @@
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -43,10 +45,12 @@
* Helps with handling camera-related gestures (for example, double-tap the power button to launch
* the camera).
*/
+@SysUISingleton
class CameraGestureHelper @Inject constructor(
private val context: Context,
private val centralSurfaces: CentralSurfaces,
private val keyguardStateController: KeyguardStateController,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val packageManager: PackageManager,
private val activityManager: ActivityManager,
private val activityStarter: ActivityStarter,
@@ -133,7 +137,7 @@
centralSurfaces.startLaunchTransitionTimeout()
// Call this to make sure the keyguard is ready to be dismissed once the next intent is
// handled by the OS (in our case it is the activity we started right above)
- centralSurfaces.readyForKeyguardDone()
+ statusBarKeyguardViewManager.readyForKeyguardDone()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 08e1e9a..f77f989 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -29,78 +29,21 @@
void setShowingAod(boolean showingAod);
/** */
- void onNotificationStartDraggingDown();
-
- /** */
- void onNotificationStopDraggingDown();
-
- /** */
- void setNotificationExpanded();
-
- /** */
- void onQsDown();
-
- /** */
boolean shouldEnforceBouncer();
/** */
- void onTrackingStarted(boolean secure);
-
- /** */
- void onTrackingStopped();
-
- /** */
- void onLeftAffordanceOn();
-
- /** */
- void onCameraOn();
-
- /** */
- void onAffordanceSwipingStarted(boolean rightCorner);
-
- /** */
- void onAffordanceSwipingAborted();
-
- /** */
- void onStartExpandingFromPulse();
-
- /** */
- void onExpansionFromPulseStopped();
-
- /** */
void onScreenOnFromTouch();
/** */
boolean isReportingEnabled();
/** */
- void onUnlockHintStarted();
-
- /** */
- void onCameraHintStarted();
-
- /** */
- void onLeftAffordanceHintStarted();
-
- /** */
void onScreenTurningOn();
/** */
void onScreenOff();
/** */
- void onNotificationStopDismissing();
-
- /** */
- void onNotificationDismissed();
-
- /** */
- void onNotificationStartDismissing();
-
- /** */
- void onNotificationDoubleTap(boolean accepted, float dx, float dy);
-
- /** */
void onBouncerShown();
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt
new file mode 100644
index 0000000..3eaad1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorActual.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 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.classifier
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FalsingCollectorActual
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index f335d1d..c0ee71c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -29,59 +29,11 @@
}
@Override
- public void onNotificationStartDraggingDown() {
- }
-
- @Override
- public void onNotificationStopDraggingDown() {
- }
-
- @Override
- public void setNotificationExpanded() {
- }
-
- @Override
- public void onQsDown() {
- }
-
- @Override
public boolean shouldEnforceBouncer() {
return false;
}
@Override
- public void onTrackingStarted(boolean secure) {
- }
-
- @Override
- public void onTrackingStopped() {
- }
-
- @Override
- public void onLeftAffordanceOn() {
- }
-
- @Override
- public void onCameraOn() {
- }
-
- @Override
- public void onAffordanceSwipingStarted(boolean rightCorner) {
- }
-
- @Override
- public void onAffordanceSwipingAborted() {
- }
-
- @Override
- public void onStartExpandingFromPulse() {
- }
-
- @Override
- public void onExpansionFromPulseStopped() {
- }
-
- @Override
public void onScreenOnFromTouch() {
}
@@ -91,18 +43,6 @@
}
@Override
- public void onUnlockHintStarted() {
- }
-
- @Override
- public void onCameraHintStarted() {
- }
-
- @Override
- public void onLeftAffordanceHintStarted() {
- }
-
- @Override
public void onScreenTurningOn() {
}
@@ -111,22 +51,6 @@
}
@Override
- public void onNotificationStopDismissing() {
- }
-
- @Override
- public void onNotificationDismissed() {
- }
-
- @Override
- public void onNotificationStartDismissing() {
- }
-
- @Override
- public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
- }
-
- @Override
public void onBouncerShown() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 6a021f6..39c01f7 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -103,10 +103,6 @@
private final BatteryStateChangeCallback mBatteryListener = new BatteryStateChangeCallback() {
@Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- }
-
- @Override
public void onWirelessChargingChanged(boolean isWirelessCharging) {
if (isWirelessCharging || mDockManager.isDocked()) {
mProximitySensor.pause();
@@ -169,34 +165,21 @@
@Override
public void onSuccessfulUnlock() {
+ logDebug("REAL: onSuccessfulUnlock");
mFalsingManager.onSuccessfulUnlock();
sessionEnd();
}
@Override
public void setShowingAod(boolean showingAod) {
+ logDebug("REAL: setShowingAod(" + showingAod + ")");
mShowingAod = showingAod;
updateSessionActive();
}
- @Override
- public void onNotificationStartDraggingDown() {
- }
-
- @Override
- public void onNotificationStopDraggingDown() {
- }
-
- @Override
- public void setNotificationExpanded() {
- }
-
- @Override
- public void onQsDown() {
- }
-
@VisibleForTesting
void onQsExpansionChanged(Boolean expanded) {
+ logDebug("REAL: onQsExpansionChanged(" + expanded + ")");
if (expanded) {
unregisterSensors();
} else if (mSessionStarted) {
@@ -210,39 +193,8 @@
}
@Override
- public void onTrackingStarted(boolean secure) {
- }
-
- @Override
- public void onTrackingStopped() {
- }
-
- @Override
- public void onLeftAffordanceOn() {
- }
-
- @Override
- public void onCameraOn() {
- }
-
- @Override
- public void onAffordanceSwipingStarted(boolean rightCorner) {
- }
-
- @Override
- public void onAffordanceSwipingAborted() {
- }
-
- @Override
- public void onStartExpandingFromPulse() {
- }
-
- @Override
- public void onExpansionFromPulseStopped() {
- }
-
- @Override
public void onScreenOnFromTouch() {
+ logDebug("REAL: onScreenOnFromTouch");
onScreenTurningOn();
}
@@ -252,52 +204,28 @@
}
@Override
- public void onUnlockHintStarted() {
- }
-
- @Override
- public void onCameraHintStarted() {
- }
-
- @Override
- public void onLeftAffordanceHintStarted() {
- }
-
- @Override
public void onScreenTurningOn() {
+ logDebug("REAL: onScreenTurningOn");
mScreenOn = true;
updateSessionActive();
}
@Override
public void onScreenOff() {
+ logDebug("REAL: onScreenOff");
mScreenOn = false;
updateSessionActive();
}
@Override
- public void onNotificationStopDismissing() {
- }
-
- @Override
- public void onNotificationDismissed() {
- }
-
- @Override
- public void onNotificationStartDismissing() {
- }
-
- @Override
- public void onNotificationDoubleTap(boolean accepted, float dx, float dy) {
- }
-
- @Override
public void onBouncerShown() {
+ logDebug("REAL: onBouncerShown");
unregisterSensors();
}
@Override
public void onBouncerHidden() {
+ logDebug("REAL: onBouncerHidden");
if (mSessionStarted) {
registerSensors();
}
@@ -305,6 +233,7 @@
@Override
public void onTouchEvent(MotionEvent ev) {
+ logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
avoidGesture();
return;
@@ -334,6 +263,7 @@
@Override
public void onMotionEventComplete() {
+ logDebug("REAL: onMotionEventComplete");
// We must delay processing the completion because of the way Android handles click events.
// It generally delays executing them immediately, instead choosing to give the UI a chance
// to respond to touch events before acknowledging the click. As such, we must also delay,
@@ -350,6 +280,7 @@
@Override
public void avoidGesture() {
+ logDebug("REAL: avoidGesture");
mAvoidGesture = true;
if (mPendingDownEvent != null) {
mPendingDownEvent.recycle();
@@ -359,6 +290,7 @@
@Override
public void cleanup() {
+ logDebug("REAL: cleanup");
unregisterSensors();
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -368,11 +300,13 @@
@Override
public void updateFalseConfidence(FalsingClassifier.Result result) {
+ logDebug("REAL: updateFalseConfidence(" + result.isFalse() + ")");
mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
}
@Override
public void onA11yAction() {
+ logDebug("REAL: onA11yAction");
if (mPendingDownEvent != null) {
mPendingDownEvent.recycle();
mPendingDownEvent = null;
@@ -427,17 +361,13 @@
static void logDebug(String msg) {
- logDebug(msg, null);
- }
-
- static void logDebug(String msg, Throwable throwable) {
if (DEBUG) {
- Log.d(TAG, msg, throwable);
+ logDebug(msg);
}
}
private static class ProximityEventImpl implements FalsingManager.ProximityEvent {
- private ThresholdSensorEvent mThresholdSensorEvent;
+ private final ThresholdSensorEvent mThresholdSensorEvent;
ProximityEventImpl(ThresholdSensorEvent thresholdSensorEvent) {
mThresholdSensorEvent = thresholdSensorEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
new file mode 100644
index 0000000..e5b404f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 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.classifier
+
+import android.view.MotionEvent
+import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class FalsingCollectorNoOp @Inject constructor() : FalsingCollector {
+ override fun onSuccessfulUnlock() {
+ logDebug("NOOP: onSuccessfulUnlock")
+ }
+
+ override fun setShowingAod(showingAod: Boolean) {
+ logDebug("NOOP: setShowingAod($showingAod)")
+ }
+
+ override fun shouldEnforceBouncer(): Boolean = false
+
+ override fun onScreenOnFromTouch() {
+ logDebug("NOOP: onScreenOnFromTouch")
+ }
+
+ override fun isReportingEnabled(): Boolean = false
+
+ override fun onScreenTurningOn() {
+ logDebug("NOOP: onScreenTurningOn")
+ }
+
+ override fun onScreenOff() {
+ logDebug("NOOP: onScreenOff")
+ }
+
+ override fun onBouncerShown() {
+ logDebug("NOOP: onBouncerShown")
+ }
+
+ override fun onBouncerHidden() {
+ logDebug("NOOP: onBouncerHidden")
+ }
+
+ override fun onTouchEvent(ev: MotionEvent) {
+ logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
+ }
+
+ override fun onMotionEventComplete() {
+ logDebug("NOOP: onMotionEventComplete")
+ }
+
+ override fun avoidGesture() {
+ logDebug("NOOP: avoidGesture")
+ }
+
+ override fun cleanup() {
+ logDebug("NOOP: cleanup")
+ }
+
+ override fun updateFalseConfidence(result: FalsingClassifier.Result) {
+ logDebug("NOOP: updateFalseConfidence(${result.isFalse})")
+ }
+
+ override fun onA11yAction() {
+ logDebug("NOOP: onA11yAction")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index c7f3b2d..3195d09 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -22,19 +22,21 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.phone.NotificationTapHelper;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.ElementsIntoSet;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Named;
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
/** Dagger Module for Falsing. */
@Module
public interface FalsingModule {
@@ -45,10 +47,20 @@
String DOUBLE_TAP_TIMEOUT_MS = "falsing_double_tap_timeout_ms";
String IS_FOLDABLE_DEVICE = "falsing_foldable_device";
- /** */
- @Binds
+ /** Provides the actual {@link FalsingCollector} if the scene container feature is off. */
+ @Provides
@SysUISingleton
- FalsingCollector bindsFalsingCollector(FalsingCollectorImpl impl);
+ static FalsingCollector providesFalsingCollectorLegacy(
+ FalsingCollectorImpl impl,
+ FalsingCollectorNoOp noOp,
+ FeatureFlagsClassic featureFlags) {
+ return featureFlags.isEnabled(Flags.SCENE_CONTAINER) ? noOp : impl;
+ }
+
+ /** Provides the actual {@link FalsingCollector}. */
+ @Binds
+ @FalsingCollectorActual
+ FalsingCollector bindsFalsingCollectorActual(FalsingCollectorImpl impl);
/** */
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
new file mode 100644
index 0000000..2e861c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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.classifier.domain.interactor
+
+import android.view.MotionEvent
+import com.android.systemui.classifier.FalsingClassifier
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * Exposes the subset of the [FalsingCollector] API that's required by external callers.
+ *
+ * E.g. methods of [FalsingCollector] that are not exposed by this class don't need to be invoked by
+ * external callers as they're already called by the scene framework.
+ */
+@SysUISingleton
+class FalsingInteractor
+@Inject
+constructor(
+ @FalsingCollectorActual private val collector: FalsingCollector,
+) {
+ /**
+ * Notifies of a [MotionEvent] that passed through the UI.
+ *
+ * Must call [onMotionEventComplete] when done with this event.
+ */
+ fun onTouchEvent(event: MotionEvent) = collector.onTouchEvent(event)
+
+ /**
+ * Notifies that a [MotionEvent] has finished being dispatched through the UI.
+ *
+ * Must be called after each call to [onTouchEvent].
+ */
+ fun onMotionEventComplete() = collector.onMotionEventComplete()
+
+ /**
+ * Instructs the falsing system to ignore the rest of the current input gesture; automatically
+ * resets when another gesture is started (with the next down event).
+ */
+ fun avoidGesture() = collector.avoidGesture()
+
+ /**
+ * Inserts the given [result] into the falsing system, affecting future runs of the classifier
+ * as if this was a result that had organically happened before.
+ */
+ fun updateFalseConfidence(
+ result: FalsingClassifier.Result,
+ ) = collector.updateFalseConfidence(result)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index c3369da..970b475 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -16,10 +16,10 @@
package com.android.systemui.communal.ui.view.layout.blueprints
-import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
import javax.inject.Inject
/** Blueprint for communal mode. */
@@ -28,13 +28,10 @@
class DefaultCommunalBlueprint
@Inject
constructor(
- private val defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
+ defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
) : KeyguardBlueprint {
override val id: String = COMMUNAL
-
- override fun apply(constraintSet: ConstraintSet) {
- defaultCommunalWidgetSection.apply(constraintSet)
- }
+ override val sections: Array<KeyguardSection> = arrayOf(defaultCommunalWidgetSection)
companion object {
const val COMMUNAL = "communal"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
index b0e3132..4fb9384 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
@@ -17,18 +17,47 @@
package com.android.systemui.communal.ui.view.layout.sections
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.communal.ui.adapter.CommunalWidgetViewAdapter
+import com.android.systemui.communal.ui.binder.CommunalWidgetViewBinder
+import com.android.systemui.communal.ui.viewmodel.CommunalWidgetViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import dagger.Lazy
import javax.inject.Inject
-class DefaultCommunalWidgetSection @Inject constructor() : KeyguardSection {
+class DefaultCommunalWidgetSection
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+ private val keyguardRootView: KeyguardRootView,
+ private val communalWidgetViewModel: CommunalWidgetViewModel,
+ private val communalWidgetViewAdapter: CommunalWidgetViewAdapter,
+ private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+) : KeyguardSection {
private val widgetAreaViewId = R.id.communal_widget_wrapper
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
+ return
+ }
- override fun apply(constraintSet: ConstraintSet) {
+ CommunalWidgetViewBinder.bind(
+ keyguardRootView,
+ communalWidgetViewModel,
+ communalWidgetViewAdapter,
+ keyguardBlueprintInteractor.get(),
+ )
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(widgetAreaViewId, WRAP_CONTENT)
constrainHeight(widgetAreaViewId, WRAP_CONTENT)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
index d3174f1..adb0bf3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -71,14 +71,9 @@
private var resolved: Boolean = false
@WorkerThread
- fun resolvePanelActivity(
- allowAllApps: Boolean = false
- ) {
+ fun resolvePanelActivity() {
if (resolved) return
resolved = true
- val validPackages = context.resources
- .getStringArray(R.array.config_controlsPreferredPackages)
- if (componentName.packageName !in validPackages && !allowAllApps) return
panelActivity = _panelActivity?.let {
val resolveInfos = mPm.queryIntentActivitiesAsUser(
Intent().setComponent(it),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 94e5633..88b9612 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -16,7 +16,6 @@
package com.android.systemui.controls.dagger
-import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.controller.ControlsController
@@ -44,10 +43,9 @@
@Inject
constructor(
@ControlsFeatureEnabled private val featureEnabled: Boolean,
- private val context: Context,
- private val lazyControlsController: Lazy<ControlsController>,
- private val lazyControlsUiController: Lazy<ControlsUiController>,
- private val lazyControlsListingController: Lazy<ControlsListingController>,
+ lazyControlsController: Lazy<ControlsController>,
+ lazyControlsUiController: Lazy<ControlsUiController>,
+ lazyControlsListingController: Lazy<ControlsListingController>,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -55,27 +53,25 @@
optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration>
) {
+ private val controlsController: Optional<ControlsController> =
+ optionalIf(isEnabled(), lazyControlsController)
+ private val controlsUiController: Optional<ControlsUiController> =
+ optionalIf(isEnabled(), lazyControlsUiController)
+ private val controlsListingController: Optional<ControlsListingController> =
+ optionalIf(isEnabled(), lazyControlsListingController)
+
val canShowWhileLockedSetting: StateFlow<Boolean> =
controlsSettingsRepository.canShowControlsInLockscreen
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl())
- fun getControlsController(): Optional<ControlsController> {
- return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
- }
+ fun getControlsController(): Optional<ControlsController> = controlsController
- fun getControlsUiController(): Optional<ControlsUiController> {
- return if (featureEnabled) Optional.of(lazyControlsUiController.get()) else Optional.empty()
- }
+ fun getControlsUiController(): Optional<ControlsUiController> = controlsUiController
- fun getControlsListingController(): Optional<ControlsListingController> {
- return if (featureEnabled) {
- Optional.of(lazyControlsListingController.get())
- } else {
- Optional.empty()
- }
- }
+ fun getControlsListingController(): Optional<ControlsListingController> =
+ controlsListingController
/** @return true if controls are feature-enabled and the user has the setting enabled */
fun isEnabled() = featureEnabled
@@ -118,4 +114,11 @@
fun getTileImageId(): Int {
return controlsTileResourceConfiguration.getTileImageId()
}
+
+ private fun <T : Any> optionalIf(condition: Boolean, provider: Lazy<T>): Optional<T> =
+ if (condition) {
+ Optional.of(provider.get())
+ } else {
+ Optional.empty()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 83bec66..74e1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -33,7 +33,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.asIndenting
@@ -125,9 +124,8 @@
private fun updateServices(newServices: List<ControlsServiceInfo>) {
if (activityTaskManagerProxy.supportsMultiWindow(context)) {
- val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
newServices.forEach {
- it.resolvePanelActivity(allowAllApps) }
+ it.resolvePanelActivity() }
}
if (newServices != availableServices) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 3562477..968c10e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentStartableModule;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.AospPolicyModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -95,6 +96,7 @@
@Module(includes = {
AospPolicyModule.class,
BatterySaverModule.class,
+ CollapsedStatusBarFragmentStartableModule.class,
GestureModule.class,
MediaModule.class,
MultiUserUtilsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6fdb4ca..dcacd09 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -22,6 +22,7 @@
import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.PerUser;
+import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
@@ -140,6 +141,7 @@
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
getUnfoldLatencyTracker().init();
+ getConnectingDisplayViewModel().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
getUnfoldTransitionProgressProvider().ifPresent((progressProvider) ->
@@ -229,6 +231,11 @@
NearbyMediaDevicesManager getNearbyMediaDevicesManager();
/**
+ * Creates a ConnectingDisplayViewModel
+ */
+ ConnectingDisplayViewModel getConnectingDisplayViewModel();
+
+ /**
* Returns {@link CoreStartable}s that should be started with the application.
*/
Map<Class<?>, Provider<CoreStartable>> getStartables();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 1b2a9eb..7ce7ce94 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -23,6 +23,7 @@
import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
@@ -346,4 +347,9 @@
abstract fun bindStatusBarHeadsUpChangeListener(
impl: StatusBarHeadsUpChangeListener
): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(BackActionInteractor::class)
+ abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index ade7684..96ec654 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -109,7 +109,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LetterboxModule;
import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -219,7 +218,6 @@
WalletModule.class
},
subcomponents = {
- CentralSurfacesComponent.class,
ComplicationComponent.class,
NavigationBarComponent.class,
NotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index b18f7bf..e7bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -22,6 +22,7 @@
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
import android.os.Handler
+import android.util.Log
import android.view.Display
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -34,6 +35,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
@@ -41,6 +43,13 @@
interface DisplayRepository {
/** Provides a nullable set of displays. */
val displays: Flow<Set<Display>>
+
+ /**
+ * Pending display id that can be enabled/disabled.
+ *
+ * When `null`, it means there is no pending display waiting to be enabled.
+ */
+ val pendingDisplayId: Flow<Int?>
}
@SysUISingleton
@@ -85,8 +94,60 @@
initialValue = getDisplays()
)
- fun getDisplays(): Set<Display> =
+ private fun getDisplays(): Set<Display> =
traceSection("DisplayRepository#getDisplays()") {
displayManager.displays?.toSet() ?: emptySet()
}
+
+ override val pendingDisplayId: Flow<Int?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DisplayConnectionListener {
+ private val pendingIds = mutableSetOf<Int>()
+ override fun onDisplayConnected(id: Int) {
+ pendingIds += id
+ trySend(id)
+ }
+
+ override fun onDisplayDisconnected(id: Int) {
+ if (id in pendingIds) {
+ pendingIds -= id
+ trySend(null)
+ } else {
+ Log.e(
+ TAG,
+ "onDisplayDisconnected received for unknown display. " +
+ "id=$id, knownIds=$pendingIds"
+ )
+ }
+ }
+ }
+ displayManager.registerDisplayListener(
+ callback,
+ backgroundHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineDispatcher)
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
+ )
+
+ private companion object {
+ const val TAG = "DisplayRepository"
+ }
+}
+
+/** Used to provide default implementations for all methods. */
+private interface DisplayConnectionListener : DisplayListener {
+
+ override fun onDisplayConnected(id: Int) {}
+ override fun onDisplayDisconnected(id: Int) {}
+ override fun onDisplayAdded(id: Int) {}
+ override fun onDisplayRemoved(id: Int) {}
+ override fun onDisplayChanged(id: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index 4b957c7..ef6fa26 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -16,12 +16,17 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.util.traceSection
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -37,18 +42,32 @@
*/
val connectedDisplayState: Flow<State>
+ /** Pending display that can be enabled to be used by the system. */
+ val pendingDisplay: Flow<PendingDisplay?>
+
/** Possible connected display state. */
enum class State {
DISCONNECTED,
CONNECTED,
CONNECTED_SECURE,
}
+
+ /** Represents a connected display that has not been enabled yet. */
+ interface PendingDisplay {
+ /** Enables the display, making it available to the system. */
+ fun enable()
+
+ /** Disables the display, making it unavailable to the system. */
+ fun disable()
+ }
}
@SysUISingleton
class ConnectedDisplayInteractorImpl
@Inject
constructor(
+ private val displayManager: DisplayManager,
+ keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
) : ConnectedDisplayInteractor {
@@ -70,4 +89,31 @@
}
}
.distinctUntilChanged()
+
+ // Provides the pending display only if the lockscreen is unlocked
+ override val pendingDisplay: Flow<PendingDisplay?> =
+ displayRepository.pendingDisplayId.combine(keyguardRepository.isKeyguardUnlocked) {
+ pendingDisplayId,
+ keyguardUnlocked ->
+ if (pendingDisplayId != null && keyguardUnlocked) {
+ pendingDisplayId.toPendingDisplay()
+ } else {
+ null
+ }
+ }
+
+ private fun Int.toPendingDisplay() =
+ object : PendingDisplay {
+ val id = this@toPendingDisplay
+ override fun enable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.enableConnectedDisplay(id)
+ }
+ }
+ override fun disable() {
+ traceSection("DisplayRepository#enable($id)") {
+ displayManager.disableConnectedDisplay(id)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
new file mode 100644
index 0000000..174c6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import com.android.systemui.R
+
+/** Dialog used to decide what to do with a connected display. */
+class MirroringConfirmationDialog(
+ context: Context,
+ private val onStartMirroringClickListener: View.OnClickListener,
+ private val onDismissClickListener: View.OnClickListener,
+) : Dialog(context, R.style.Theme_SystemUI_Dialog) {
+
+ private lateinit var mirrorButton: TextView
+ private lateinit var dismissButton: TextView
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ setGravity(Gravity.BOTTOM)
+ }
+ setContentView(R.layout.connected_display_dialog)
+ setCanceledOnTouchOutside(true)
+ mirrorButton =
+ requireViewById<TextView>(R.id.enable_display).apply {
+ setOnClickListener(onStartMirroringClickListener)
+ }
+ dismissButton =
+ requireViewById<TextView>(R.id.cancel).apply {
+ setOnClickListener(onDismissClickListener)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
new file mode 100644
index 0000000..ece33b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.display.ui.viewmodel
+
+import android.app.Dialog
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
+import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Shows/hides a dialog to allow the user to decide whether to use the external display for
+ * mirroring.
+ */
+@SysUISingleton
+class ConnectingDisplayViewModel
+@Inject
+constructor(
+ private val context: Context,
+ private val connectedDisplayInteractor: ConnectedDisplayInteractor,
+ @Application private val scope: CoroutineScope,
+) {
+
+ private var dialog: Dialog? = null
+
+ /** Starts listening for pending displays. */
+ fun init() {
+ connectedDisplayInteractor.pendingDisplay
+ .onEach { pendingDisplay ->
+ if (pendingDisplay == null) {
+ hideDialog()
+ } else {
+ showDialog(pendingDisplay)
+ }
+ }
+ .launchIn(scope)
+ }
+
+ private fun showDialog(pendingDisplay: PendingDisplay) {
+ hideDialog()
+ dialog =
+ MirroringConfirmationDialog(
+ context,
+ onStartMirroringClickListener = {
+ pendingDisplay.enable()
+ hideDialog()
+ },
+ onDismissClickListener = { hideDialog() }
+ )
+ .apply { show() }
+ }
+
+ private fun hideDialog() {
+ dialog?.hide()
+ dialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6b578ba..0fb1b48 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -39,6 +39,10 @@
@JvmField val TEAMFOOD = unreleasedFlag("teamfood")
// 100 - notification
+ // TODO(b/297792660): Tracking Bug
+ val ADD_TRANSIENT_HUN_IN_STACK_STATE_ANIMATOR =
+ unreleasedFlag("add_transient_hun_in_stack_state_animator", teamfood = false)
+
// TODO(b/254512751): Tracking Bug
val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
unreleasedFlag("notification_pipeline_developer_logging")
@@ -204,6 +208,11 @@
@JvmField
val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
+ /** Inflate and bind views upon emitting a blueprint value . */
+ // TODO(b/297365780): Tracking Bug
+ @JvmField
+ val LAZY_INFLATE_KEYGUARD = unreleasedFlag("lazy_inflate_keyguard", teamfood = true)
+
/** Enables UI updates for AI wallpapers in the wallpaper picker. */
// TODO(b/267722622): Tracking Bug
@JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
@@ -394,7 +403,7 @@
// TODO(b/290676905): Tracking Bug
val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS =
- unreleasedFlag("new_shade_carrier_group_mobile_icons")
+ unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true)
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
@@ -492,11 +501,6 @@
@Keep
@JvmField
- val WM_DESKTOP_WINDOWING =
- sysPropBooleanFlag("persist.wm.debug.desktop_mode", default = false)
-
- @Keep
- @JvmField
val WM_CAPTION_ON_SHELL =
sysPropBooleanFlag("persist.wm.debug.caption_on_shell", default = true)
@@ -634,9 +638,6 @@
// 1900
@JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
- // 2000 - device controls
- @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
-
// 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
// TODO(b/259264861): Tracking Bug
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
@@ -767,7 +768,7 @@
/** Enable the share wifi button in Quick Settings internet dialog. */
@JvmField
- val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
+ val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button", teamfood = true)
/** Enable haptic slider component in the brightness slider */
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index c41b5e4..2856011 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -366,6 +366,52 @@
}
}
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param icon an [Icon] representing the source of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ suspend fun loadSize(icon: Icon, context: Context): Size? =
+ withContext(backgroundDispatcher) { loadSizeSync(icon, context) }
+
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param icon an [Icon] representing the source of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ @WorkerThread
+ fun loadSizeSync(icon: Icon, context: Context): Size? {
+ return when (icon.type) {
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ val source = ImageDecoder.createSource(context.contentResolver, icon.uri)
+ loadSizeSync(source)
+ }
+ else -> null
+ }
+ }
+
+ /**
+ * Obtains the image size from the image header, without decoding the full image.
+ *
+ * @param source [ImageDecoder.Source] of the image
+ * @return the [Size] if it could be determined from the image header, or `null` otherwise
+ */
+ @WorkerThread
+ fun loadSizeSync(source: ImageDecoder.Source): Size? {
+ return try {
+ ImageDecoder.decodeHeader(source).size
+ } catch (e: IOException) {
+ Log.w(TAG, "Failed to load source $source", e)
+ return null
+ } catch (e: DecodeException) {
+ Log.w(TAG, "Failed to decode source $source", e)
+ return null
+ }
+ }
+
companion object {
const val TAG = "ImageLoader"
@@ -452,7 +498,7 @@
* originate from other processes so we need to make sure we load them from the right
* package source.
*
- * @return [Resources] to load the icon drawble or null if icon doesn't carry a resource or
+ * @return [Resources] to load the icon drawable or null if icon doesn't carry a resource or
* the resource package couldn't be resolved.
*/
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
new file mode 100644
index 0000000..629b361
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderEventProducer.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 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.haptics.slider
+
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+/** An event producer for a Seekable element such as the Android [SeekBar] */
+class SeekableSliderEventProducer : SliderEventProducer, OnSeekBarChangeListener {
+
+ /** The current event reported by a SeekBar */
+ private val _currentEvent = MutableStateFlow(SliderEvent(SliderEventType.NOTHING, 0f))
+
+ override fun produceEvents(): Flow<SliderEvent> = _currentEvent.asStateFlow()
+
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+ val eventType =
+ if (fromUser) SliderEventType.PROGRESS_CHANGE_BY_USER
+ else SliderEventType.PROGRESS_CHANGE_BY_PROGRAM
+
+ _currentEvent.value = SliderEvent(eventType, normalizeProgress(seekBar, progress))
+ }
+
+ /**
+ * Normalize the integer progress of a SeekBar to the range from 0F to 1F.
+ *
+ * @param[seekBar] The SeekBar that reports a progress.
+ * @param[progress] The integer progress of the SeekBar within its min and max values.
+ * @return The progress in the range from 0F to 1F.
+ */
+ private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float {
+ if (seekBar.max == seekBar.min) {
+ return 1.0f
+ }
+ val range = seekBar.max - seekBar.min
+ return (progress - seekBar.min) / range.toFloat()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, previousEvent.currentProgress)
+ }
+ }
+
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ _currentEvent.update { previousEvent ->
+ SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, previousEvent.currentProgress)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
new file mode 100644
index 0000000..1377b29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.haptics.slider
+
+import androidx.annotation.FloatRange
+
+/**
+ * An event arising from a slider.
+ *
+ * @property[type] The type of event. Must be one of [SliderEventType].
+ * @property[currentProgress] The current progress of the slider normalized to the range between 0F
+ * and 1F (inclusive).
+ */
+data class SliderEvent(
+ val type: SliderEventType,
+ @FloatRange(from = 0.0, to = 1.0) val currentProgress: Float
+)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
new file mode 100644
index 0000000..8b17e86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventProducer.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.haptics.slider
+
+import kotlinx.coroutines.flow.Flow
+
+/** Defines a producer of [SliderEvent] to be consumed as a [Flow] */
+interface SliderEventProducer {
+
+ /**
+ * Produce a stream of [SliderEvent]
+ *
+ * @return A [Flow] of [SliderEvent] produced from the operation of a slider.
+ */
+ fun produceEvents(): Flow<SliderEvent>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
new file mode 100644
index 0000000..413e277
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.haptics.slider
+
+/** The type of a [SliderEvent]. */
+enum class SliderEventType {
+ /* No event. */
+ NOTHING,
+ /* The slider has captured a touch input and is tracking touch events. */
+ STARTED_TRACKING_TOUCH,
+ /* The slider progress is changing due to user touch input. */
+ PROGRESS_CHANGE_BY_USER,
+ /* The slider progress is changing programmatically. */
+ PROGRESS_CHANGE_BY_PROGRAM,
+ /* The slider has stopped tracking touch events. */
+ STOPPED_TRACKING_TOUCH,
+ /* The external (not touch) stimulus that was modifying the slider progress has stopped. */
+ EXTERNAL_STIMULUS_RELEASE,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 9d2771e..f6add9c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -298,18 +298,18 @@
}
@Inject
- public KeyguardService(KeyguardViewMediator keyguardViewMediator,
- KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
- ScreenOnCoordinator screenOnCoordinator,
- ShellTransitions shellTransitions,
- DisplayTracker displayTracker,
- WindowManagerLockscreenVisibilityViewModel
- wmLockscreenVisibilityViewModel,
- WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
- KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
- KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
- @Application CoroutineScope scope,
- FeatureFlags featureFlags) {
+ public KeyguardService(
+ KeyguardViewMediator keyguardViewMediator,
+ KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
+ ScreenOnCoordinator screenOnCoordinator,
+ ShellTransitions shellTransitions,
+ DisplayTracker displayTracker,
+ WindowManagerLockscreenVisibilityViewModel wmLockscreenVisibilityViewModel,
+ WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+ KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+ KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+ @Application CoroutineScope scope,
+ FeatureFlags featureFlags) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 1cd8795..6bc9abf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -17,10 +17,15 @@
package com.android.systemui.keyguard
+import android.content.Context
import android.content.res.Configuration
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.CoreStartable
import com.android.systemui.R
@@ -37,6 +42,7 @@
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
@@ -92,6 +98,9 @@
private val communalWidgetViewModel: CommunalWidgetViewModel,
private val communalWidgetViewAdapter: CommunalWidgetViewAdapter,
private val notificationStackScrollerLayoutController: NotificationStackScrollLayoutController,
+ private val context: Context,
+ private val keyguardIndicationController: KeyguardIndicationController,
+ private val lockIconViewController: LockIconViewController,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -100,22 +109,41 @@
private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
private var settingsPopupMenuHandle: DisposableHandle? = null
- private var keyguardStatusViewController: KeyguardStatusViewController? = null
+ var keyguardStatusViewController: KeyguardStatusViewController? = null
+ get() {
+ if (field == null) {
+ val statusViewComponent =
+ keyguardStatusViewComponentFactory.build(
+ LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, null)
+ as KeyguardStatusView
+ )
+ val controller = statusViewComponent.keyguardStatusViewController
+ controller.init()
+ field = controller
+ }
+
+ return field
+ }
override fun start() {
- bindKeyguardRootView()
- val notificationPanel =
- notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
- unbindKeyguardBottomArea(notificationPanel)
- bindIndicationArea()
- bindLockIconView(notificationPanel)
- bindKeyguardStatusView(notificationPanel)
- setupNotificationStackScrollLayout(notificationPanel)
- bindLeftShortcut()
- bindRightShortcut()
- bindAmbientIndicationArea()
- bindSettingsPopupMenu()
- bindCommunalWidgetArea()
+ if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) {
+ keyguardRootView.removeAllViews()
+ initializeViews()
+ } else {
+ bindKeyguardRootView()
+ val notificationPanel =
+ notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
+ unbindKeyguardBottomArea(notificationPanel)
+ bindIndicationArea()
+ bindLockIconView(notificationPanel)
+ bindKeyguardStatusView(notificationPanel)
+ setupNotificationStackScrollLayout(notificationPanel)
+ bindLeftShortcut()
+ bindRightShortcut()
+ bindAmbientIndicationArea()
+ bindSettingsPopupMenu()
+ bindCommunalWidgetArea()
+ }
KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
keyguardBlueprintCommandListener.start()
@@ -164,6 +192,14 @@
)
}
+ /** Initialize views so that corresponding controllers have a view set. */
+ private fun initializeViews() {
+ val indicationArea = KeyguardIndicationArea(context, null)
+ keyguardIndicationController.setIndicationArea(indicationArea)
+
+ lockIconViewController.setLockIconView(LockIconView(context, null))
+ }
+
private fun bindKeyguardRootView() {
rootViewHandle?.dispose()
rootViewHandle =
@@ -186,6 +222,9 @@
keyguardRootView.findViewById<View?>(R.id.lock_icon_view)?.let {
keyguardRootView.removeView(it)
}
+ legacyParent.requireViewById<LockIconView>(R.id.lock_icon_view).let {
+ lockIconViewController.setLockIconView(it)
+ }
}
}
@@ -307,6 +346,5 @@
* Temporary, to allow NotificationPanelViewController to use the same instance while code is
* migrated: b/288242803
*/
- fun getKeyguardStatusViewController() = keyguardStatusViewController
fun getKeyguardRootView() = keyguardRootView
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e983a05..2b4dc81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3207,7 +3207,7 @@
* visible
*/
public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
- Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
+ Log.d(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation");
if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard
+ " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
@@ -3222,6 +3222,13 @@
// Post layout changes to the next frame, so we don't hang at the end of the animation.
DejankUtils.postAfterTraversal(() -> {
+ if (!mPM.isInteractive()) {
+ Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" +
+ "Not interactive after traversal. Don't hide the keyguard. This means we " +
+ "re-locked the device during unlock.");
+ return;
+ }
+
onKeyguardExitFinished();
if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index 7234757..f91ae74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -17,13 +17,10 @@
package com.android.systemui.keyguard.data.repository
-import android.view.View
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.core.view.children
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
import java.io.PrintWriter
@@ -95,26 +92,3 @@
blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
}
}
-
-/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
-interface KeyguardBlueprint {
- val id: String
- val shouldRemoveUnconstrainedViews: Boolean
- get() = true
-
- fun apply(constraintLayout: ConstraintSet)
- fun removeUnConstrainedViews(constraintLayout: ConstraintLayout, constraintSet: ConstraintSet) {
- constraintLayout.children
- .map { it.id }
- .filterNot { constraintSet.knownIds.contains(it) }
- .forEach { constraintSet.setVisibility(it, View.GONE) }
- }
-}
-
-/**
- * Lower level modules that determine constraints for a particular section in the lockscreen root
- * view.
- */
-interface KeyguardSection {
- fun apply(constraintSet: ConstraintSet)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 42cd3a5..d399e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -158,7 +158,7 @@
val lastDozeTapToWakePosition: StateFlow<Point?>
/** Observable for the [StatusBarState] */
- val statusBarState: Flow<StatusBarState>
+ val statusBarState: StateFlow<StatusBarState>
/** Observable for device wake/sleep state */
val wakefulness: StateFlow<WakefulnessModel>
@@ -520,23 +520,29 @@
return keyguardBypassController.bypassEnabled
}
- override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
- val callback =
- object : StatusBarStateController.StateListener {
- override fun onStateChanged(state: Int) {
- trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
- }
+ // TODO(b/297345631): Expose this at the interactor level instead so that it can be powered by
+ // [SceneInteractor] when scenes are ready.
+ override val statusBarState: StateFlow<StatusBarState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(state: Int) {
+ trySendWithFailureLogging(
+ statusBarStateIntToObject(state),
+ TAG,
+ "state"
+ )
+ }
+ }
+
+ statusBarStateController.addCallback(callback)
+ awaitClose { statusBarStateController.removeCallback(callback) }
}
-
- statusBarStateController.addCallback(callback)
- trySendWithFailureLogging(
- statusBarStateIntToObject(statusBarStateController.getState()),
- TAG,
- "initial state"
- )
-
- awaitClose { statusBarStateController.removeCallback(callback) }
- }
+ .stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ statusBarStateIntToObject(statusBarStateController.state)
+ )
override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
fun dispatchUpdate() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index ff0db34..714add4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -50,14 +50,28 @@
listenForOccludedToAodOrDozing()
listenForOccludedToGone()
listenForOccludedToAlternateBouncer()
+ listenForOccludedToPrimaryBouncer()
+ }
+
+ private fun listenForOccludedToPrimaryBouncer() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (
+ isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.OCCLUDED
+ ) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
}
private fun listenForOccludedToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
.sample(transitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (isAbleToDream, keyguardState) = pair
+ .collect { (isAbleToDream, keyguardState) ->
if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
startTransitionTo(KeyguardState.DREAMING)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index e13f675..0c05a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -78,28 +78,37 @@
/** Position information for the shared notification container. */
val sharedNotificationContainerPosition =
MutableStateFlow(SharedNotificationContainerPosition())
+
/**
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
* all.
*/
val dozeAmount: Flow<Float> = repository.linearDozeAmount
+
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
+
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
+
/** Whether Always-on Display mode is available. */
val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
+
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
+
/**
* Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
* but not vice-versa.
*/
val isDreaming: Flow<Boolean> = repository.isDreaming
+
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+
/** Whether the system is dreaming and the active dream is hosted in lockscreen */
val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
+
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
val callback =
@@ -148,18 +157,25 @@
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+
/** Whether the keyguard is unlocked or not. */
val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
+
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+
/** Whether the primary bouncer is showing or not. */
val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
+
/** Whether the alternate bouncer is showing or not. */
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
new file mode 100644
index 0000000..659c5f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.keyguard.shared.model
+
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+
+/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
+interface KeyguardBlueprint {
+ val id: String
+ val sections: Array<KeyguardSection>
+
+ fun addViews(constraintLayout: ConstraintLayout) {
+ sections.forEach { it.addViews(constraintLayout) }
+ }
+
+ fun applyConstraints(constraintSet: ConstraintSet) {
+ sections.forEach { it.applyConstraints(constraintSet) }
+ }
+
+ fun onDestroy() {
+ sections.forEach { it.onDestroy() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
new file mode 100644
index 0000000..19f50de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 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.keyguard.shared.model
+
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+
+/**
+ * Lower level modules that determine constraints for a particular section in the lockscreen root
+ * view.
+ */
+interface KeyguardSection {
+ fun addViews(constraintLayout: ConstraintLayout)
+ fun applyConstraints(constraintSet: ConstraintSet)
+ fun onDestroy() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index fb685da..c8a04fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -19,18 +19,20 @@
import android.os.PowerManager
/** The reason we're waking up or going to sleep, such as pressing the power button. */
-enum class WakeSleepReason {
+enum class WakeSleepReason(
+ val isTouch: Boolean,
+) {
/** The physical power button was pressed to wake up or sleep the device. */
- POWER_BUTTON,
+ POWER_BUTTON(isTouch = false),
/** The user has tapped or double tapped to wake the screen. */
- TAP,
+ TAP(isTouch = true),
/** The user performed some sort of gesture to wake the screen. */
- GESTURE,
+ GESTURE(isTouch = true),
/** Something else happened to wake up or sleep the device. */
- OTHER;
+ OTHER(isTouch = false);
companion object {
fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index e40c279..c340e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -21,6 +21,7 @@
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.view.children
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
@@ -36,16 +37,28 @@
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
- Trace.beginSection("KeyguardBlueprintController#applyBlueprint")
+ Trace.beginSection("KeyguardBlueprint#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
- ConstraintSet().apply {
- clone(constraintLayout)
- val emptyLayout = ConstraintSet.Layout()
- knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
- blueprint?.apply(this)
- blueprint?.removeUnConstrainedViews(constraintLayout, this)
- applyTo(constraintLayout)
+ if (blueprint != viewModel.currentBluePrint) {
+ viewModel.currentBluePrint?.onDestroy()
}
+ val constraintSet =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ val emptyLayout = ConstraintSet.Layout()
+ knownIds.forEach {
+ getConstraint(it).layout.copyFrom(emptyLayout)
+ }
+ blueprint.addViews(constraintLayout)
+ blueprint.applyConstraints(this)
+ applyTo(constraintLayout)
+ }
+ // Remove all unconstrained views.
+ constraintLayout.children
+ .filterNot { constraintSet.knownIds.contains(it.id) }
+ .forEach { constraintLayout.removeView(it) }
+
+ viewModel.currentBluePrint = blueprint
Trace.endSection()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 2814732..8b0b0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -78,6 +78,22 @@
}
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
+ }
+
+ if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ launch {
+ viewModel.translationY.collect {
+ val statusView =
+ view.requireViewById<View>(R.id.keyguard_status_view)
+ statusView.translationY = it
+ }
+ }
+ }
+ }
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
launch {
viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
view.animate().cancel()
@@ -111,18 +127,6 @@
}
}
}
-
- launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
- }
-
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
- launch {
- viewModel.translationY.collect {
- val statusView =
- view.requireViewById<View>(R.id.keyguard_status_view)
- statusView.translationY = it
- }
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 41c1c96..2cfc478 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -34,6 +34,7 @@
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
@@ -45,12 +46,12 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
-import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
@@ -109,6 +110,7 @@
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardStateController: KeyguardStateController,
+ private val defaultShortcutsSection: DefaultShortcutsSection,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -119,6 +121,7 @@
KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
false,
)
+
/** [shouldHideClock] here means that we never create and bind the clock views */
private val shouldHideClock: Boolean =
bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
@@ -176,7 +179,6 @@
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
setupKeyguardRootView(rootView)
- setupShortcuts(rootView)
} else {
setUpBottomArea(rootView)
}
@@ -348,14 +350,14 @@
FrameLayout.LayoutParams.MATCH_PARENT,
),
)
- KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
- keyguardBlueprintInteractor.refreshBlueprint()
+ setupShortcuts(keyguardRootView)
}
- private fun setupShortcuts(rootView: FrameLayout) {
+ private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
+ defaultShortcutsSection.addShortcutViews(keyguardRootView)
shortcutsBindings.add(
KeyguardQuickAffordanceViewBinder.bind(
- rootView.requireViewById(R.id.start_button),
+ keyguardRootView.requireViewById(R.id.start_button),
quickAffordancesCombinedViewModel.startButton,
keyguardRootViewModel.alpha,
falsingManager,
@@ -367,7 +369,7 @@
shortcutsBindings.add(
KeyguardQuickAffordanceViewBinder.bind(
- rootView.requireViewById(R.id.end_button),
+ keyguardRootView.requireViewById(R.id.end_button),
quickAffordancesCombinedViewModel.endButton,
keyguardRootViewModel.alpha,
falsingManager,
@@ -516,11 +518,11 @@
// is dark or a light.
// TODO(b/277832214) we can potentially simplify this code by checking for
// wallpaperColors being null in the if clause above and removing the many ?.
- val wallpaperColorScheme =
- wallpaperColors?.let { ColorScheme(it, /* darkTheme= */ false) }
+ val wallpaperColorScheme = wallpaperColors?.let { ColorScheme(it, darkTheme = false) }
val lightClockColor = wallpaperColorScheme?.accent1?.s100
val darkClockColor = wallpaperColorScheme?.accent2?.s600
- /** Note that when [wallpaperColors] is null, isWallpaperDark is true. */
+
+ // Note that when [wallpaperColors] is null, isWallpaperDark is true.
val isWallpaperDark: Boolean =
(wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
clock.events.onSeedColorChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 518df07..5a15fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -17,12 +17,15 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
-import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
@@ -39,24 +42,34 @@
class DefaultKeyguardBlueprint
@Inject
constructor(
- private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
- private val defaultLockIconSection: DefaultLockIconSection,
- private val defaultShortcutsSection: DefaultShortcutsSection,
- private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
- private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
- private val defaultStatusViewSection: DefaultStatusViewSection,
- private val splitShadeGuidelines: SplitShadeGuidelines,
+ defaultIndicationAreaSection: DefaultIndicationAreaSection,
+ defaultLockIconSection: DefaultLockIconSection,
+ defaultShortcutsSection: DefaultShortcutsSection,
+ defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+ defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+ defaultStatusViewSection: DefaultStatusViewSection,
+ defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
+ splitShadeGuidelines: SplitShadeGuidelines,
+ private val featureFlags: FeatureFlags,
) : KeyguardBlueprint {
override val id: String = DEFAULT
- override fun apply(constraintSet: ConstraintSet) {
- defaultIndicationAreaSection.apply(constraintSet)
- defaultLockIconSection.apply(constraintSet)
- defaultShortcutsSection.apply(constraintSet)
- defaultAmbientIndicationAreaSection.apply(constraintSet)
- defaultSettingsPopupMenuSection.apply(constraintSet)
- defaultStatusViewSection.apply(constraintSet)
- splitShadeGuidelines.apply(constraintSet)
+ override val sections =
+ arrayOf(
+ defaultIndicationAreaSection,
+ defaultLockIconSection,
+ defaultShortcutsSection,
+ defaultAmbientIndicationAreaSection,
+ defaultSettingsPopupMenuSection,
+ defaultStatusViewSection,
+ defaultNotificationStackScrollLayoutSection,
+ splitShadeGuidelines,
+ )
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) {
+ super.addViews(constraintLayout)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
index 07f316b..fda4c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt
@@ -18,7 +18,7 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
import com.android.systemui.communal.ui.view.layout.blueprints.DefaultCommunalBlueprint
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 54c2796..5ef625e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -17,16 +17,14 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import javax.inject.Inject
@@ -36,31 +34,28 @@
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
constructor(
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
- private val defaultLockIconSection: DefaultLockIconSection,
- private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
- private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
- private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
- private val defaultShortcutsSection: DefaultShortcutsSection,
- private val defaultStatusViewSection: DefaultStatusViewSection,
- private val splitShadeGuidelines: SplitShadeGuidelines,
+ defaultIndicationAreaSection: DefaultIndicationAreaSection,
+ defaultLockIconSection: DefaultLockIconSection,
+ defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+ defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+ alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
+ defaultStatusViewSection: DefaultStatusViewSection,
+ splitShadeGuidelines: SplitShadeGuidelines,
+ defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
- override fun apply(constraintSet: ConstraintSet) {
- defaultIndicationAreaSection.apply(constraintSet)
- defaultLockIconSection.apply(constraintSet)
- defaultAmbientIndicationAreaSection.apply(constraintSet)
- defaultSettingsPopupMenuSection.apply(constraintSet)
- if (keyguardUpdateMonitor.isUdfpsSupported) {
- alignShortcutsToUdfpsSection.apply(constraintSet)
- } else {
- defaultShortcutsSection.apply(constraintSet)
- }
- defaultStatusViewSection.apply(constraintSet)
- splitShadeGuidelines.apply(constraintSet)
- }
+ override val sections =
+ arrayOf(
+ defaultIndicationAreaSection,
+ defaultLockIconSection,
+ defaultAmbientIndicationAreaSection,
+ defaultSettingsPopupMenuSection,
+ alignShortcutsToUdfpsSection,
+ defaultStatusViewSection,
+ defaultNotificationStackScrollLayoutSection,
+ splitShadeGuidelines,
+ )
companion object {
const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 156b9f3..587c6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
@@ -26,12 +27,58 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-class AlignShortcutsToUdfpsSection @Inject constructor(@Main private val resources: Resources) :
- KeyguardSection {
- override fun apply(constraintSet: ConstraintSet) {
+class AlignShortcutsToUdfpsSection
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val featureFlags: FeatureFlags,
+ private val keyguardQuickAffordancesCombinedViewModel:
+ KeyguardQuickAffordancesCombinedViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val falsingManager: FalsingManager,
+ private val indicationController: KeyguardIndicationController,
+ private val vibratorHelper: VibratorHelper,
+) : BaseShortcutsSection(), KeyguardSection {
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ addLeftShortcut(constraintLayout)
+ addRightShortcut(constraintLayout)
+ leftShortcutHandle =
+ KeyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.start_button),
+ keyguardQuickAffordancesCombinedViewModel.startButton,
+ keyguardRootViewModel.alpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ rightShortcutHandle =
+ KeyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.end_button),
+ keyguardQuickAffordancesCombinedViewModel.endButton,
+ keyguardRootViewModel.alpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt
new file mode 100644
index 0000000..db0cf5a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout.sections
+
+import android.view.View
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+
+/** Base class for sections that add lockscreen shortcuts. */
+abstract class BaseShortcutsSection : KeyguardSection {
+ protected open var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+ protected open var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {}
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {}
+
+ override fun onDestroy() {
+ leftShortcutHandle?.destroy()
+ rightShortcutHandle?.destroy()
+ }
+
+ protected open fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+ if (constraintLayout.findViewById<View>(R.id.start_button) != null) return
+
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.start_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+
+ protected open fun addRightShortcut(constraintLayout: ConstraintLayout) {
+ if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+ val padding =
+ constraintLayout.resources.getDimensionPixelSize(
+ R.dimen.keyguard_affordance_fixed_padding
+ )
+ val view =
+ LaunchableImageView(constraintLayout.context, null).apply {
+ id = R.id.end_button
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ background =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_bg,
+ context.theme
+ )
+ foreground =
+ ResourcesCompat.getDrawable(
+ context.resources,
+ R.drawable.keyguard_bottom_affordance_selected_border,
+ context.theme
+ )
+ visibility = View.INVISIBLE
+ setPadding(padding, padding, padding, padding)
+ }
+ constraintLayout.addView(view)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index abf25a2..f8455c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -17,7 +17,10 @@
package com.android.systemui.keyguard.ui.view.layout.sections
+import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
@@ -28,13 +31,44 @@
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import javax.inject.Inject
class DefaultAmbientIndicationAreaSection
@Inject
-constructor(private val keyguardUpdateMonitor: KeyguardUpdateMonitor) : KeyguardSection {
- override fun apply(constraintSet: ConstraintSet) {
+constructor(
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val featureFlags: FeatureFlags,
+ private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+) : KeyguardSection {
+ private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (constraintLayout.findViewById<View>(R.id.ambient_indication_container) == null) {
+ val view =
+ LayoutInflater.from(constraintLayout.context)
+ .inflate(R.layout.ambient_indication, constraintLayout, false)
+
+ constraintLayout.addView(view)
+ }
+
+ ambientIndicationAreaHandle =
+ KeyguardAmbientIndicationAreaViewBinder.bind(
+ constraintLayout,
+ keyguardAmbientIndicationViewModel,
+ keyguardRootViewModel,
+ )
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(R.id.ambient_indication_container, MATCH_PARENT)
@@ -59,4 +93,8 @@
}
}
}
+
+ override fun onDestroy() {
+ ambientIndicationAreaHandle?.destroy()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index dee7ed5..f04bfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -18,17 +18,53 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
+import android.view.View
import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
+import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.statusbar.KeyguardIndicationController
import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
-class DefaultIndicationAreaSection @Inject constructor(private val context: Context) :
- KeyguardSection {
+class DefaultIndicationAreaSection
+@Inject
+constructor(
+ private val context: Context,
+ private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val indicationController: KeyguardIndicationController,
+ private val featureFlags: FeatureFlags,
+) : KeyguardSection {
private val indicationAreaViewId = R.id.keyguard_indication_area
+ private var indicationAreaHandle: DisposableHandle? = null
- override fun apply(constraintSet: ConstraintSet) {
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (constraintLayout.findViewById<View>(indicationAreaViewId) == null) {
+ val view = KeyguardIndicationArea(context, null)
+ constraintLayout.addView(view)
+ }
+
+ indicationAreaHandle =
+ KeyguardIndicationAreaBinder.bind(
+ constraintLayout,
+ keyguardIndicationAreaViewModel,
+ keyguardRootViewModel,
+ indicationController,
+ featureFlags,
+ )
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(indicationAreaViewId, ViewGroup.LayoutParams.MATCH_PARENT)
constrainHeight(indicationAreaViewId, ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -53,4 +89,8 @@
)
}
}
+
+ override fun onDestroy() {
+ indicationAreaHandle?.dispose()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
index 461faec..3d62f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
@@ -21,13 +21,20 @@
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
+import android.view.View
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconView
+import com.android.keyguard.LockIconViewController
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.shade.NotificationPanelView
import javax.inject.Inject
class DefaultLockIconSection
@@ -37,16 +44,30 @@
private val authController: AuthController,
private val windowManager: WindowManager,
private val context: Context,
+ private val notificationPanelView: NotificationPanelView,
+ private val featureFlags: FeatureFlags,
+ private val lockIconViewController: LockIconViewController,
) : KeyguardSection {
private val lockIconViewId = R.id.lock_icon_view
- override fun apply(constraintSet: ConstraintSet) {
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+ notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
+ notificationPanelView.removeView(it)
+ }
+ if (constraintLayout.findViewById<View>(R.id.lock_icon_view) == null) {
+ val view = LockIconView(context, null).apply { id = R.id.lock_icon_view }
+ constraintLayout.addView(view)
+ lockIconViewController.setLockIconView(view)
+ }
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx =
context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
- val mDefaultPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding)
- val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
val bounds = windowManager.currentWindowMetrics.bounds
val widthPixels = bounds.right.toFloat()
val heightPixels = bounds.bottom.toFloat()
@@ -57,12 +78,7 @@
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
- centerLockIcon(
- udfpsLocation,
- authController.udfpsRadius,
- scaledPadding,
- constraintSet
- )
+ centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet)
}
} else {
centerLockIcon(
@@ -71,19 +87,13 @@
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
),
lockIconRadiusPx * scaleFactor,
- scaledPadding,
constraintSet,
)
}
}
@VisibleForTesting
- internal fun centerLockIcon(
- center: Point,
- radius: Float,
- drawablePadding: Int,
- constraintSet: ConstraintSet
- ) {
+ internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
val sensorRect =
Rect().apply {
set(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
new file mode 100644
index 0000000..a203e41d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout.sections
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
+import javax.inject.Inject
+
+class DefaultNotificationStackScrollLayoutSection
+@Inject
+constructor(
+ private val featureFlags: FeatureFlags,
+ private val notificationPanelView: NotificationPanelView,
+ private val sharedNotificationContainer: SharedNotificationContainer,
+ private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
+ private val controller: NotificationStackScrollLayoutController,
+) : KeyguardSection {
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ // This moves the existing NSSL view to a different parent, as the controller is a
+ // singleton and recreating it has other bad side effects
+ notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ sharedNotificationContainer.addNotificationStackScrollLayout(it)
+ SharedNotificationContainerBinder.bind(
+ sharedNotificationContainer,
+ sharedNotificationContainerViewModel,
+ controller,
+ )
+ }
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index ad1e4f8..660cc96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -18,20 +18,65 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
+import android.view.LayoutInflater
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import androidx.core.view.isVisible
import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
+import kotlinx.coroutines.DisposableHandle
-class DefaultSettingsPopupMenuSection @Inject constructor(@Main private val resources: Resources) :
- KeyguardSection {
- override fun apply(constraintSet: ConstraintSet) {
+class DefaultSettingsPopupMenuSection
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val featureFlags: FeatureFlags,
+ private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val activityStarter: ActivityStarter,
+) : KeyguardSection {
+ private var settingsPopupMenuHandle: DisposableHandle? = null
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ if (constraintLayout.findViewById<View?>(R.id.keyguard_settings_button) == null) {
+ val view =
+ LayoutInflater.from(constraintLayout.context)
+ .inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false)
+ .apply {
+ id = R.id.keyguard_settings_button
+ isVisible = false
+ alpha = 0f
+ } as LaunchableLinearLayout
+ constraintLayout.addView(view)
+ }
+
+ settingsPopupMenuHandle =
+ KeyguardSettingsViewBinder.bind(
+ constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
+ keyguardSettingsMenuViewModel,
+ vibratorHelper,
+ activityStarter,
+ )
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
val horizontalOffsetMargin =
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
@@ -51,6 +96,11 @@
BOTTOM,
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
)
+ setVisibility(R.id.keyguard_settings_button, View.GONE)
}
}
+
+ override fun onDestroy() {
+ settingsPopupMenuHandle?.dispose()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index db4653d..965910a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.res.Resources
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
@@ -25,12 +26,58 @@
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
-class DefaultShortcutsSection @Inject constructor(@Main private val resources: Resources) :
- KeyguardSection {
- override fun apply(constraintSet: ConstraintSet) {
+class DefaultShortcutsSection
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val featureFlags: FeatureFlags,
+ private val keyguardQuickAffordancesCombinedViewModel:
+ KeyguardQuickAffordancesCombinedViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val falsingManager: FalsingManager,
+ private val indicationController: KeyguardIndicationController,
+ private val vibratorHelper: VibratorHelper,
+) : BaseShortcutsSection(), KeyguardSection {
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+ addLeftShortcut(constraintLayout)
+ addRightShortcut(constraintLayout)
+ leftShortcutHandle =
+ KeyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.start_button),
+ keyguardQuickAffordancesCombinedViewModel.startButton,
+ keyguardRootViewModel.alpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ rightShortcutHandle =
+ KeyguardQuickAffordanceViewBinder.bind(
+ constraintLayout.requireViewById(R.id.end_button),
+ keyguardQuickAffordancesCombinedViewModel.endButton,
+ keyguardRootViewModel.alpha,
+ falsingManager,
+ vibratorHelper,
+ ) {
+ indicationController.showTransientIndication(it)
+ }
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
val horizontalOffsetMargin =
@@ -50,4 +97,15 @@
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
}
}
+
+ /** Method to add shortcuts without applying any data binding. */
+ fun addShortcutViews(constraintLayout: ConstraintLayout) {
+ addLeftShortcut(constraintLayout)
+ addRightShortcut(constraintLayout)
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ applyConstraints(this)
+ applyTo(constraintLayout)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index f1f5973..321d7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -18,23 +18,76 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.KeyguardStatusView
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.KeyguardSection
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.Utils
+import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-class DefaultStatusViewSection @Inject constructor(private val context: Context) : KeyguardSection {
+class DefaultStatusViewSection
+@Inject
+constructor(
+ private val context: Context,
+ private val featureFlags: FeatureFlags,
+ private val notificationPanelView: NotificationPanelView,
+ private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+ private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>,
+ private val notificationPanelViewController: Lazy<NotificationPanelViewController>,
+ private val keyguardMediaController: KeyguardMediaController,
+) : KeyguardSection {
private val statusViewId = R.id.keyguard_status_view
- override fun apply(constraintSet: ConstraintSet) {
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
+ // Disable one of them
+ if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ notificationPanelView.findViewById<View>(statusViewId)?.let {
+ notificationPanelView.removeView(it)
+ }
+ if (constraintLayout.findViewById<View>(statusViewId) == null) {
+ val keyguardStatusView =
+ (LayoutInflater.from(context)
+ .inflate(R.layout.keyguard_status_view, constraintLayout, false)
+ as KeyguardStatusView)
+ .apply { clipChildren = false }
+
+ val statusViewComponent =
+ keyguardStatusViewComponentFactory.build(keyguardStatusView)
+ val controller = statusViewComponent.keyguardStatusViewController
+ controller.init()
+ constraintLayout.addView(keyguardStatusView)
+ keyguardMediaController.attachSplitShadeContainer(
+ keyguardStatusView.requireViewById<ViewGroup>(R.id.status_view_media_container)
+ )
+ keyguardViewConfigurator.get().keyguardStatusViewController = controller
+ notificationPanelViewController.get().updateStatusBarViewController()
+ }
+ }
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(statusViewId, MATCH_CONSTRAINT)
constrainHeight(statusViewId, WRAP_CONTENT)
@@ -52,4 +105,9 @@
setMargin(statusViewId, TOP, margin)
}
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override fun onDestroy() {
+ keyguardViewConfigurator.get().keyguardStatusViewController = null
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
index 668b17f..bd629d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
@@ -18,23 +18,17 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
-import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.R
-import com.android.systemui.keyguard.data.repository.KeyguardSection
-import javax.inject.Inject
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+import com.android.systemui.R
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import javax.inject.Inject
-class SplitShadeGuidelines @Inject constructor(private val context: Context) :
- KeyguardSection {
+class SplitShadeGuidelines @Inject constructor(private val context: Context) : KeyguardSection {
+ override fun addViews(constraintLayout: ConstraintLayout) {}
- override fun apply(constraintSet: ConstraintSet) {
+ override fun applyConstraints(constraintSet: ConstraintSet) {
constraintSet.apply {
// For use on large screens, it will provide a guideline vertically in the center to
// enable items to be aligned on the left or right sides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index 5e9e553..e2bfc36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -17,13 +17,13 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import javax.inject.Inject
-@SysUISingleton
class KeyguardBlueprintViewModel
@Inject
constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
+ var currentBluePrint: KeyguardBlueprint? = null
val blueprint = keyguardBlueprintInteractor.blueprint
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 93c4902..05c9323 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneKey
import javax.inject.Inject
@@ -30,7 +29,7 @@
@Inject
constructor(
authenticationInteractor: AuthenticationInteractor,
- private val bouncerInteractor: BouncerInteractor,
+ val longPress: KeyguardLongPressViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: Flow<SceneKey> =
@@ -41,9 +40,4 @@
SceneKey.Bouncer
}
}
-
- /** Notifies that the lock button on the lock screen was clicked. */
- fun onLockButtonClicked() {
- bouncerInteractor.showOrUnlockDevice()
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 8f884d24..053c9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -206,6 +206,11 @@
override fun bind(recentTasks: List<RecentTask>) {
recentsViewController.bind(recentTasks)
+ if (!hasWorkProfile()) {
+ // Make sure to refresh the adapter, to show/hide the recents view depending on whether
+ // there are recents or not.
+ mMultiProfilePagerAdapter.personalListAdapter.notifyDataSetChanged()
+ }
}
override fun returnSelectedApp(launchCookie: IBinder) {
@@ -248,9 +253,20 @@
override fun shouldGetOnlyDefaultActivities() = false
- override fun shouldShowContentPreview() = true
+ override fun shouldShowContentPreview() =
+ if (hasWorkProfile()) {
+ // When the user has a work profile, we can always set this to true, and the layout is
+ // adjusted automatically, and hide the recents view.
+ true
+ } else {
+ // When there is no work profile, we should only show the content preview if there are
+ // recents, otherwise the collapsed app selector will look empty.
+ recentsViewController.hasRecentTasks
+ }
- override fun shouldShowContentPreviewWhenEmpty(): Boolean = true
+ override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+ private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1
override fun createMyUserIdProvider(): MyUserIdProvider =
object : MyUserIdProvider() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 398dcf2..38a6a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -39,7 +39,6 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -59,6 +58,11 @@
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY as SSPACE_CARD_REPORTED__DREAM_OVERLAY
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN as SSPACE_CARD_REPORTED__LOCKSCREEN
+import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -100,7 +104,6 @@
@Main executor: DelayableExecutor,
private val mediaManager: MediaDataManager,
configurationController: ConfigurationController,
- falsingCollector: FalsingCollector,
falsingManager: FalsingManager,
dumpManager: DumpManager,
private val logger: MediaUiEventLogger,
@@ -122,6 +125,7 @@
/** Is the player currently visible (at the end of the transformation */
private var playersVisible: Boolean = false
+
/**
* The desired location where we'll be at the end of the transformation. Usually this matches
* the end location, except when we're still waiting on a state update call.
@@ -152,6 +156,7 @@
@VisibleForTesting var mediaCarousel: MediaScrollView
val mediaCarouselScrollHandler: MediaCarouselScrollHandler
val mediaFrame: ViewGroup
+
@VisibleForTesting
lateinit var settingsButton: View
private set
@@ -280,7 +285,6 @@
this::updatePageIndicatorLocation,
this::updateSeekbarListening,
this::closeGuts,
- falsingCollector,
falsingManager,
this::logSmartspaceImpression,
logger
@@ -327,23 +331,18 @@
if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
// Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
- /* ktlint-disable max-line-length */
logSmartspaceCardReported(
759, // SMARTSPACE_CARD_RECEIVED
it.mSmartspaceId,
it.mUid,
surfaces =
intArrayOf(
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SSPACE_CARD_REPORTED__LOCKSCREEN,
+ SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
rank = MediaPlayerData.getMediaPlayerIndex(key)
)
- /* ktlint-disable max-line-length */
}
if (
mediaCarouselScrollHandler.visibleToUser &&
@@ -362,24 +361,20 @@
it.mUid + systemClock.currentTimeMillis().toInt()
)
it.mIsImpressed = false
- /* ktlint-disable max-line-length */
+
logSmartspaceCardReported(
759, // SMARTSPACE_CARD_RECEIVED
it.mSmartspaceId,
it.mUid,
surfaces =
intArrayOf(
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SSPACE_CARD_REPORTED__LOCKSCREEN,
+ SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
rank = index,
receivedLatencyMillis = receivedSmartspaceCardLatency
)
- /* ktlint-disable max-line-length */
}
}
// If media container area already visible to the user, log impression for
@@ -431,19 +426,16 @@
it.mUid + systemClock.currentTimeMillis().toInt()
)
it.mIsImpressed = false
- /* ktlint-disable max-line-length */
+
logSmartspaceCardReported(
759, // SMARTSPACE_CARD_RECEIVED
it.mSmartspaceId,
it.mUid,
surfaces =
intArrayOf(
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SSPACE_CARD_REPORTED__LOCKSCREEN,
+ SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
rank = index,
receivedLatencyMillis =
@@ -451,25 +443,20 @@
data.headphoneConnectionTimeMillis)
.toInt()
)
- /* ktlint-disable max-line-length */
}
}
}
addSmartspaceMediaRecommendations(key, data, shouldPrioritize)
MediaPlayerData.getMediaPlayer(key)?.let {
- /* ktlint-disable max-line-length */
logSmartspaceCardReported(
759, // SMARTSPACE_CARD_RECEIVED
it.mSmartspaceId,
it.mUid,
surfaces =
intArrayOf(
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN,
- SysUiStatsLog
- .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY
+ SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE,
+ SSPACE_CARD_REPORTED__LOCKSCREEN,
+ SSPACE_CARD_REPORTED__DREAM_OVERLAY,
),
rank = MediaPlayerData.getMediaPlayerIndex(key),
receivedLatencyMillis =
@@ -477,7 +464,6 @@
data.headphoneConnectionTimeMillis)
.toInt()
)
- /* ktlint-disable max-line-length */
}
if (
mediaCarouselScrollHandler.visibleToUser &&
@@ -560,7 +546,10 @@
mediaCarouselScrollHandler.onSettingsButtonUpdated(settings)
settingsButton.setOnClickListener {
logger.logCarouselSettings()
- activityStarter.startActivity(settingsIntent, true /* dismissShade */)
+ activityStarter.startActivity(
+ settingsIntent,
+ /* dismissShade= */ true,
+ )
}
}
@@ -1108,7 +1097,6 @@
}
}
- @JvmOverloads
/**
* Log Smartspace events
*
@@ -1127,6 +1115,7 @@
* between headphone connection to sysUI displays media recommendation card
* @param isSwipeToDismiss whether is to log swipe-to-dismiss event
*/
+ @JvmOverloads
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
@@ -1154,21 +1143,24 @@
val cardinality = mediaContent.getChildCount()
surfaces.forEach { surface ->
- /* ktlint-disable max-line-length */
SysUiStatsLog.write(
- SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+ SMARTSPACE_CARD_REPORTED,
eventId,
instanceId,
// Deprecated, replaced with AiAi feature type so we don't need to create logging
// card type for each new feature.
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
+ SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
surface,
// Use -1 as rank value to indicate user swipe to dismiss the card
if (isSwipeToDismiss) -1 else rank,
cardinality,
- if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION
- else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED
- else 31, // MEDIA_RESUME
+ if (mediaControlKey.isSsMediaRec) {
+ 15 // MEDIA_RECOMMENDATION
+ } else if (mediaControlKey.isSsReactivated) {
+ 43 // MEDIA_RESUME_SS_ACTIVATED
+ } else {
+ 31
+ }, // MEDIA_RESUME
uid,
interactedSubcardRank,
interactedSubcardCardinality,
@@ -1176,7 +1168,7 @@
null, // Media cards cannot have subcards.
null // Media cards don't have dimensions today.
)
- /* ktlint-disable max-line-length */
+
if (DEBUG) {
Log.d(
TAG,
@@ -1259,6 +1251,7 @@
instanceId = InstanceId.fakeInstanceId(-1),
appUid = -1
)
+
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
@@ -1291,6 +1284,7 @@
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
+
// A map that tracks order of visible media players before they get reordered.
private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index ce50a11..bbb61b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -30,7 +30,6 @@
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
@@ -59,7 +58,6 @@
private var translationChangedListener: () -> Unit,
private var seekBarUpdateListener: (visibleToUser: Boolean) -> Unit,
private val closeGuts: (immediate: Boolean) -> Unit,
- private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
private val logSmartspaceImpression: (Boolean) -> Unit,
private val logger: MediaUiEventLogger
@@ -67,8 +65,10 @@
/** Is the view in RTL */
val isRtl: Boolean
get() = scrollView.isLayoutRtl
+
/** Do we need falsing protection? */
var falsingProtectionNeeded: Boolean = false
+
/** The width of the carousel */
private var carouselWidth: Int = 0
@@ -80,6 +80,7 @@
/** The content where the players are added */
private var mediaContent: ViewGroup
+
/** The gesture detector to detect touch gestures */
private val gestureDetector: GestureDetectorCompat
@@ -140,9 +141,6 @@
) = onScroll(down!!, lastMotion, distanceX)
override fun onDown(e: MotionEvent): Boolean {
- if (falsingProtectionNeeded) {
- falsingCollector.onNotificationStartDismissing()
- }
return false
}
}
@@ -263,9 +261,6 @@
private fun onTouch(motionEvent: MotionEvent): Boolean {
val isUp = motionEvent.action == MotionEvent.ACTION_UP
- if (isUp && falsingProtectionNeeded) {
- falsingCollector.onNotificationStopDismissing()
- }
if (gestureDetector.onTouchEvent(motionEvent)) {
if (isUp) {
// If this is an up and we're flinging, we don't want to have this touch reach
@@ -482,8 +477,11 @@
}
val relativeLocation =
visibleMediaIndex.toFloat() +
- if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding
- else 0f
+ if (playerWidthPlusPadding > 0) {
+ scrollInAmount.toFloat() / playerWidthPlusPadding
+ } else {
+ 0f
+ }
// Fix the location, because PageIndicator does not handle RTL internally
val location =
if (isRtl) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 64de9bd..5d732fb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -35,8 +35,8 @@
import javax.inject.Inject
/**
- * Controller that handles view of the recent apps selector in the media projection activity.
- * It is responsible for creating and updating recent apps view.
+ * Controller that handles view of the recent apps selector in the media projection activity. It is
+ * responsible for creating and updating recent apps view.
*/
@MediaProjectionAppSelectorScope
class MediaProjectionRecentsViewController
@@ -51,15 +51,21 @@
private var views: Views? = null
private var lastBoundData: List<RecentTask>? = null
+ val hasRecentTasks: Boolean
+ get() = lastBoundData?.isNotEmpty() ?: false
+
init {
taskViewSizeProvider.addCallback(this)
}
fun createView(parent: ViewGroup): ViewGroup =
- views?.root ?: createRecentViews(parent).also {
- views = it
- lastBoundData?.let { recents -> bind(recents) }
- }.root
+ views?.root
+ ?: createRecentViews(parent)
+ .also {
+ views = it
+ lastBoundData?.let { recents -> bind(recents) }
+ }
+ .root
fun bind(recentTasks: List<RecentTask>) {
views?.apply {
@@ -88,7 +94,8 @@
.inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
as ViewGroup
- val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
+ val container =
+ recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
container.setTaskHeightSize()
val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 5a1ad96..e1e1aae 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -35,11 +35,14 @@
import android.os.Temperature;
import android.os.UserHandle;
import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Slog;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
@@ -51,17 +54,13 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.io.PrintWriter;
import java.util.Arrays;
-import java.util.Optional;
import java.util.concurrent.Future;
import javax.inject.Inject;
-import dagger.Lazy;
-
@SysUISingleton
public class PowerUI implements CoreStartable, CommandQueue.Callbacks {
@@ -107,12 +106,15 @@
@VisibleForTesting int mBatteryLevel = 100;
@VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
+ private boolean mInVrMode;
+
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
private final Context mContext;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
- private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+ @Nullable
+ private final IVrManager mVrManager;
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
new WakefulnessLifecycle.Observer() {
@Override
@@ -134,17 +136,28 @@
}
};
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ mInVrMode = enabled;
+ }
+ };
+
@Inject
- public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher,
- CommandQueue commandQueue, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- WarningsUI warningsUI, EnhancedEstimates enhancedEstimates,
+ public PowerUI(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher,
+ CommandQueue commandQueue,
+ @Nullable IVrManager vrManager,
+ WarningsUI warningsUI,
+ EnhancedEstimates enhancedEstimates,
WakefulnessLifecycle wakefulnessLifecycle,
PowerManager powerManager,
UserTracker userTracker) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mVrManager = vrManager;
mWarnings = warningsUI;
mEnhancedEstimates = enhancedEstimates;
mPowerManager = powerManager;
@@ -164,7 +177,7 @@
};
final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
+ Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
@@ -199,6 +212,14 @@
});
initThermalEventListeners();
mCommandQueue.addCallback(this);
+
+ if (mVrManager != null) {
+ try {
+ mVrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener: " + e);
+ }
+ }
}
@Override
@@ -718,10 +739,7 @@
int status = temp.getStatus();
if (status >= Temperature.THROTTLING_EMERGENCY) {
- final Optional<CentralSurfaces> centralSurfacesOptional =
- mCentralSurfacesOptionalLazy.get();
- if (!centralSurfacesOptional.map(CentralSurfaces::isDeviceInVrMode)
- .orElse(false)) {
+ if (!mInVrMode) {
mWarnings.showHighTemperatureWarning();
Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
+ ", current skin status = " + status
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 809edc0..16885ed 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -19,6 +19,7 @@
import android.os.PowerManager
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -34,7 +35,7 @@
constructor(
private val repository: PowerRepository,
private val keyguardRepository: KeyguardRepository,
- private val falsingCollector: FalsingCollector,
+ @FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
new file mode 100644
index 0000000..d833e56
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepository.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.scene.data.repository
+
+import android.os.RemoteException
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Source of truth for the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityRepository
+@Inject
+constructor(
+ private val statusBarService: IStatusBarService,
+ @UiBackground private val uiBgExecutor: Executor,
+) {
+ private val _isLockscreenOrShadeVisible = MutableStateFlow(false)
+ val isLockscreenOrShadeVisible: StateFlow<Boolean> = _isLockscreenOrShadeVisible.asStateFlow()
+
+ fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+ _isLockscreenOrShadeVisible.value = visible
+ }
+
+ /**
+ * Called when the lockscreen or shade has been shown and can be interacted with so that SysUI
+ * can notify external services.
+ */
+ fun onLockscreenOrShadeInteractive(
+ shouldClearNotificationEffects: Boolean,
+ notificationCount: Int,
+ ) {
+ executeServiceCallOnUiBg {
+ statusBarService.onPanelRevealed(shouldClearNotificationEffects, notificationCount)
+ }
+ }
+
+ /**
+ * Called when the lockscreen or shade no longer can be interactecd with so that SysUI can
+ * notify external services.
+ */
+ fun onLockscreenOrShadeNotInteractive() {
+ executeServiceCallOnUiBg { statusBarService.onPanelHidden() }
+ }
+
+ private fun executeServiceCallOnUiBg(runnable: () -> Unit) {
+ uiBgExecutor.execute {
+ try {
+ runnable.invoke()
+ } catch (ex: RemoteException) {
+ // Won't fail unless the world has ended
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 342c218..45ee7be 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -168,7 +168,7 @@
* Once a transition between one scene and another passes a threshold, the UI invokes this
* method to report it, updating the value in [desiredScene] to match what the UI shows.
*/
- internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
+ fun onSceneChanged(scene: SceneModel, loggingReason: String) {
updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
new file mode 100644
index 0000000..16ffcc2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 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.scene.domain.interactor
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Business logic about the visibility of various parts of the window root view. */
+@SysUISingleton
+class WindowRootViewVisibilityInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository,
+ private val keyguardRepository: KeyguardRepository,
+ private val headsUpManager: HeadsUpManager,
+) : CoreStartable {
+
+ private var notificationPresenter: NotificationPresenter? = null
+ private var notificationsController: NotificationsController? = null
+
+ private val isNotifPresenterFullyCollapsed: Boolean
+ get() = notificationPresenter?.isPresenterFullyCollapsed ?: true
+
+ /**
+ * True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
+ * false if the bouncer is visible.
+ *
+ * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
+ */
+ val isLockscreenOrShadeVisible: StateFlow<Boolean> =
+ windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+
+ /**
+ * True if lockscreen (including AOD) or the shade is visible **and** the user is currently
+ * interacting with the device, false otherwise. Notably, false if the bouncer is visible and
+ * false if the device is asleep.
+ */
+ val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> =
+ combine(
+ isLockscreenOrShadeVisible,
+ keyguardRepository.wakefulness,
+ ) { isKeyguardAodOrShadeVisible, wakefulness ->
+ isKeyguardAodOrShadeVisible && wakefulness.isDeviceInteractive()
+ }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+ /**
+ * Sets classes that aren't easily injectable on this class.
+ *
+ * TODO(b/277762009): Inject these directly instead.
+ */
+ fun setUp(
+ presenter: NotificationPresenter?,
+ notificationsController: NotificationsController?,
+ ) {
+ this.notificationPresenter = presenter
+ this.notificationsController = notificationsController
+ }
+
+ override fun start() {
+ scope.launch {
+ isLockscreenOrShadeVisibleAndInteractive.collect { interactive ->
+ if (interactive) {
+ windowRootViewVisibilityRepository.onLockscreenOrShadeInteractive(
+ getShouldClearNotificationEffects(keyguardRepository.statusBarState.value),
+ getNotificationLoad(),
+ )
+ } else {
+ windowRootViewVisibilityRepository.onLockscreenOrShadeNotInteractive()
+ }
+ }
+ }
+ }
+
+ fun setIsLockscreenOrShadeVisible(visible: Boolean) {
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(visible)
+ }
+
+ private fun getShouldClearNotificationEffects(statusBarState: StatusBarState): Boolean {
+ return !isNotifPresenterFullyCollapsed &&
+ (statusBarState == StatusBarState.SHADE ||
+ statusBarState == StatusBarState.SHADE_LOCKED)
+ }
+
+ private fun getNotificationLoad(): Int {
+ return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
+ 1
+ } else {
+ notificationsController?.getActiveNotificationsCount() ?: 0
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1747099..7f77acc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -14,11 +14,15 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
@@ -40,7 +44,12 @@
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
@@ -61,6 +70,7 @@
private val sysUiState: SysUiState,
@DisplayId private val displayId: Int,
private val sceneLogger: SceneLogger,
+ @FalsingCollectorActual private val falsingCollector: FalsingCollector,
) : CoreStartable {
override fun start() {
@@ -69,6 +79,7 @@
hydrateVisibility()
automaticallySwitchScenes()
hydrateSystemUiState()
+ collectFalsingSignals()
} else {
sceneLogger.logFrameworkEnabled(isEnabled = false)
}
@@ -225,6 +236,66 @@
}
}
+ /** Collects and reports signals into the falsing system. */
+ private fun collectFalsingSignals() {
+ applicationScope.launch {
+ authenticationInteractor.isLockscreenDismissed.collect { isLockscreenDismissed ->
+ if (isLockscreenDismissed) {
+ falsingCollector.onSuccessfulUnlock()
+ }
+ }
+ }
+
+ applicationScope.launch {
+ keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+ falsingCollector.setShowingAod(isDozing)
+ }
+ }
+
+ applicationScope.launch {
+ keyguardInteractor.isAodAvailable
+ .flatMapLatest { isAodAvailable ->
+ if (!isAodAvailable) {
+ keyguardInteractor.wakefulnessModel
+ } else {
+ emptyFlow()
+ }
+ }
+ .map { wakefulnessModel ->
+ val wakeChange: Boolean? =
+ when (wakefulnessModel.state) {
+ WakefulnessState.STARTING_TO_WAKE -> true
+ WakefulnessState.ASLEEP -> false
+ else -> null
+ }
+ (wakeChange to wakefulnessModel.lastWakeReason).takeIf { wakeChange != null }
+ }
+ .filterNotNull()
+ .distinctUntilChangedBy { it.first }
+ .collect { (wakeChange, wakeReason) ->
+ when {
+ wakeChange == true && wakeReason.isTouch ->
+ falsingCollector.onScreenOnFromTouch()
+ wakeChange == true -> falsingCollector.onScreenTurningOn()
+ wakeChange == false -> falsingCollector.onScreenOff()
+ }
+ }
+ }
+
+ applicationScope.launch {
+ sceneInteractor.desiredScene
+ .map { it.key == SceneKey.Bouncer }
+ .distinctUntilChanged()
+ .collect { switchedToBouncerScene ->
+ if (switchedToBouncerScene) {
+ falsingCollector.onBouncerShown()
+ } else {
+ falsingCollector.onBouncerHidden()
+ }
+ }
+ }
+ }
+
private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
sceneInteractor.changeScene(
scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
index 8da1803..fcfdceb 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -29,4 +30,11 @@
@IntoMap
@ClassKey(SceneContainerStartable::class)
fun bind(impl: SceneContainerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(WindowRootViewVisibilityInteractor::class)
+ fun bindWindowRootViewVisibilityInteractor(
+ impl: WindowRootViewVisibilityInteractor
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index f9324a9..3e76607 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -110,6 +110,7 @@
// SysUI altogether.
private fun createVisibilityToggleView(otherView: View): View {
val toggleView = View(otherView.context)
+ otherView.isVisible = false
toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
toggleView.setOnClickListener {
val now = Instant.now()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index c9a73e6..ef688a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 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.scene.ui.view
import android.annotation.SuppressLint
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index c5b605a..2431660 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -16,6 +16,8 @@
package com.android.systemui.scene.ui.viewmodel
+import android.view.MotionEvent
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -30,7 +32,8 @@
class SceneContainerViewModel
@Inject
constructor(
- private val interactor: SceneInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val falsingInteractor: FalsingInteractor,
) {
/**
* Keys of all scenes in the container.
@@ -38,17 +41,17 @@
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
- val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
+ val allSceneKeys: List<SceneKey> = sceneInteractor.allSceneKeys()
/** The scene that should be rendered. */
- val currentScene: StateFlow<SceneModel> = interactor.desiredScene
+ val currentScene: StateFlow<SceneModel> = sceneInteractor.desiredScene
/** Whether the container is visible. */
- val isVisible: StateFlow<Boolean> = interactor.isVisible
+ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
/** Notifies that the UI has transitioned sufficiently to the given scene. */
fun onSceneChanged(scene: SceneModel) {
- interactor.onSceneChanged(
+ sceneInteractor.onSceneChanged(
scene = scene,
loggingReason = SCENE_TRANSITION_LOGGING_REASON,
)
@@ -60,12 +63,27 @@
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
- interactor.setTransitionState(transitionState)
+ sceneInteractor.setTransitionState(transitionState)
}
- /** Handles an event representing user input. */
- fun onUserInput() {
- interactor.onUserInput()
+ /**
+ * Notifies that a [MotionEvent] is first seen at the top of the scene container UI.
+ *
+ * Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
+ */
+ fun onMotionEvent(event: MotionEvent) {
+ sceneInteractor.onUserInput()
+ falsingInteractor.onTouchEvent(event)
+ }
+
+ /**
+ * Notifies that a [MotionEvent] that was previously sent to [onMotionEvent] has passed through
+ * the scene container UI.
+ *
+ * Call this after the [MotionEvent] propagates completely through the UI hierarchy.
+ */
+ fun onMotionEventComplete() {
+ falsingInteractor.onMotionEventComplete()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a41d6e8..8db7abf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1226,37 +1226,7 @@
private void updateViewControllers(
FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
- // Re-associate the KeyguardStatusViewController
- if (mKeyguardStatusViewController != null) {
- mKeyguardStatusViewController.onDestroy();
- }
-
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
- // Need a shared controller until mKeyguardStatusViewController can be removed from
- // here, due to important state being set in that controller. Rebind in order to pick
- // up config changes
- mKeyguardViewConfigurator.bindKeyguardStatusView(mView);
- mKeyguardStatusViewController =
- mKeyguardViewConfigurator.getKeyguardStatusViewController();
- } else {
- KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById(
- R.id.keyguard_status_view);
- KeyguardStatusViewComponent statusViewComponent =
- mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
- mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
- mKeyguardStatusViewController.init();
- }
- mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
- mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- int oldHeight = oldBottom - oldTop;
- if (v.getHeight() != oldHeight) {
- mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
- }
- });
-
- updateClockAppearance();
-
+ updateStatusBarViewController();
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
// Otherwise, NPV can get into a state where some of the views are still hidden
@@ -1286,6 +1256,40 @@
}
}
+ /** Updates the StatusBarViewController and updates any that depend on it. */
+ public void updateStatusBarViewController() {
+ // Re-associate the KeyguardStatusViewController
+ if (mKeyguardStatusViewController != null) {
+ mKeyguardStatusViewController.onDestroy();
+ }
+
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ // Need a shared controller until mKeyguardStatusViewController can be removed from
+ // here, due to important state being set in that controller. Rebind in order to pick
+ // up config changes
+ mKeyguardStatusViewController =
+ mKeyguardViewConfigurator.getKeyguardStatusViewController();
+ } else {
+ KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById(
+ R.id.keyguard_status_view);
+ KeyguardStatusViewComponent statusViewComponent =
+ mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
+ mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
+ mKeyguardStatusViewController.init();
+ }
+
+ mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+ mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ int oldHeight = oldBottom - oldTop;
+ if (v.getHeight() != oldHeight) {
+ mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
+ }
+ });
+
+ updateClockAppearance();
+ }
+
@Override
public void updateResources() {
final boolean newSplitShadeEnabled =
@@ -1403,7 +1407,8 @@
updateViewControllers(userAvatarView, keyguardUserSwitcherView);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) && !mFeatureFlags.isEnabled(
+ Flags.LAZY_INFLATE_KEYGUARD)) {
attachSplitShadeMediaPlayerContainer(
mKeyguardViewConfigurator.getKeyguardRootView()
.findViewById(R.id.status_view_media_container));
@@ -2818,7 +2823,6 @@
}
private void onTrackingStarted() {
- mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
endClosing();
mTracking = true;
mTrackingStartedListener.onTrackingStarted();
@@ -2834,7 +2838,6 @@
}
private void onTrackingStopped(boolean expand) {
- mFalsingCollector.onTrackingStopped();
mTracking = false;
maybeStopTrackingExpansionFromStatusBar(expand);
@@ -2888,7 +2891,6 @@
@VisibleForTesting
void onUnlockHintStarted() {
- mFalsingCollector.onUnlockHintStarted();
mKeyguardIndicationController.showActionToUnlock();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index ad9df72..4a76dd0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -21,7 +21,6 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import android.app.IActivityManager;
@@ -52,6 +51,7 @@
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -76,6 +76,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -104,6 +105,7 @@
private final float mKeyguardMaxRefreshRate;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardBypassController mKeyguardBypassController;
+ private final Executor mBackgroundExecutor;
private final AuthController mAuthController;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
@@ -141,6 +143,7 @@
ConfigurationController configurationController,
KeyguardViewMediator keyguardViewMediator,
KeyguardBypassController keyguardBypassController,
+ @Background Executor backgroundExecutor,
SysuiColorExtractor colorExtractor,
DumpManager dumpManager,
KeyguardStateController keyguardStateController,
@@ -159,6 +162,7 @@
mLpChanged = new LayoutParams();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardBypassController = keyguardBypassController;
+ mBackgroundExecutor = backgroundExecutor;
mColorExtractor = colorExtractor;
mScreenOffAnimationController = screenOffAnimationController;
dumpManager.registerDumpable(this);
@@ -520,13 +524,14 @@
applyWindowLayoutParams();
if (mHasTopUi != mHasTopUiChanged) {
- whitelistIpcs(() -> {
+ mHasTopUi = mHasTopUiChanged;
+ mBackgroundExecutor.execute(() -> {
try {
mActivityManager.setHasTopUi(mHasTopUiChanged);
} catch (RemoteException e) {
Log.e(TAG, "Failed to call setHasTopUi", e);
}
- mHasTopUi = mHasTopUiChanged;
+
});
}
notifyStateChangedCallbacks();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3a916cf..0f85c76 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.back.domain.interactor.BackActionInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
@@ -43,6 +44,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor;
@@ -65,6 +67,8 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.DozeScrimController;
+import com.android.systemui.statusbar.phone.DozeServiceHost;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
@@ -81,7 +85,7 @@
* Controller for {@link NotificationShadeWindowView}.
*/
@SysUISingleton
-public class NotificationShadeWindowViewController {
+public class NotificationShadeWindowViewController implements Dumpable {
private static final String TAG = "NotifShadeWindowVC";
private final FalsingCollector mFalsingCollector;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -118,6 +122,8 @@
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarViewController mStatusBarViewController;
private final CentralSurfaces mService;
+ private final DozeServiceHost mDozeServiceHost;
+ private final DozeScrimController mDozeScrimController;
private final BackActionInteractor mBackActionInteractor;
private final PowerInteractor mPowerInteractor;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -152,6 +158,8 @@
StatusBarWindowStateController statusBarWindowStateController,
LockIconViewController lockIconViewController,
CentralSurfaces centralSurfaces,
+ DozeServiceHost dozeServiceHost,
+ DozeScrimController dozeScrimController,
BackActionInteractor backActionInteractor,
PowerInteractor powerInteractor,
NotificationShadeWindowController controller,
@@ -160,6 +168,7 @@
NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
ShadeLogger shadeLogger,
+ DumpManager dumpManager,
PulsingGestureListener pulsingGestureListener,
LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -187,8 +196,9 @@
mLockIconViewController = lockIconViewController;
mBackActionInteractor = backActionInteractor;
mShadeLogger = shadeLogger;
- mLockIconViewController.init();
mService = centralSurfaces;
+ mDozeServiceHost = dozeServiceHost;
+ mDozeScrimController = dozeScrimController;
mPowerInteractor = powerInteractor;
mNotificationShadeWindowController = controller;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -226,6 +236,9 @@
progressProvider -> progressProvider.addCallback(
mDisableSubpixelTextTransitionListener));
}
+
+ lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view));
+ dumpManager.registerDumpable(this);
}
/**
@@ -332,7 +345,7 @@
}
if (mStatusBarStateController.isDozing()) {
- mService.extendDozePulse();
+ mDozeScrimController.extendPulse();
}
mLockIconViewController.onTouchEvent(
ev,
@@ -391,7 +404,7 @@
@Override
public boolean shouldInterceptTouchEvent(MotionEvent ev) {
- if (mStatusBarStateController.isDozing() && !mService.isPulsing()
+ if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing()
&& !mDockManager.isDocked()) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mShadeLogger.d("NSWVC: capture all touch events in always-on");
@@ -445,7 +458,7 @@
public boolean handleTouchEvent(MotionEvent ev) {
boolean handled = false;
if (mStatusBarStateController.isDozing()) {
- handled = !mService.isPulsing();
+ handled = !mDozeServiceHost.isPulsing();
}
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
@@ -533,6 +546,7 @@
mAmbientState.setSwipingUp(false);
}
+ @Override
public void dump(PrintWriter pw, String[] args) {
pw.print(" mExpandAnimationRunning=");
pw.println(mExpandAnimationRunning);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index c9c911b..b2bbffd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -67,7 +67,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.classifier.Classifier;
-import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -147,7 +146,6 @@
private final MediaHierarchyManager mMediaHierarchyManager;
private final AmbientState mAmbientState;
private final RecordingController mRecordingController;
- private final FalsingCollector mFalsingCollector;
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final ShadeLogger mShadeLog;
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@@ -335,7 +333,6 @@
AmbientState ambientState,
RecordingController recordingController,
FalsingManager falsingManager,
- FalsingCollector falsingCollector,
AccessibilityManager accessibilityManager,
LockscreenGestureLogger lockscreenGestureLogger,
MetricsLogger metricsLogger,
@@ -381,7 +378,6 @@
mAmbientState = ambientState;
mRecordingController = recordingController;
mFalsingManager = falsingManager;
- mFalsingCollector = falsingCollector;
mAccessibilityManager = accessibilityManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
@@ -1660,7 +1656,6 @@
}
}
if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
- mFalsingCollector.onQsDown();
mShadeLog.logMotionEvent(event,
"handleQsDown: down action, QS tracking enabled");
mTracking = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d7a3392..9a3e4e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -30,6 +30,7 @@
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -59,6 +60,7 @@
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
private final LogBuffer mTouchLog;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
@@ -83,6 +85,7 @@
CommandQueue commandQueue,
@Main Executor mainExecutor,
@ShadeTouchLog LogBuffer touchLog,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -97,6 +100,7 @@
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
mTouchLog = touchLog;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
@@ -391,6 +395,7 @@
private void notifyVisibilityChanged(boolean visible) {
mShadeVisibilityListener.visibilityChanged(visible);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(visible);
}
private void notifyExpandedVisibleChanged(boolean expandedVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 2532bad..b553f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -99,9 +99,6 @@
/** Sets the view's alpha to max. */
fun resetAlpha()
- /** Sets progress of the predictive back animation. */
- fun onBackProgressed(progressFraction: Float)
-
/** @see com.android.systemui.keyguard.ScreenLifecycle.Observer.onScreenTurningOn */
fun onScreenTurningOn()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 182a676..1121834 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -155,6 +155,9 @@
/** Called when Back gesture has been committed (i.e. a back event has definitely occurred) */
fun onBackPressed()
+ /** Sets progress of the predictive back animation. */
+ fun onBackProgressed(progressFraction: Float)
+
/** Sets whether the status bar launch animation is currently running. */
fun setIsLaunchAnimationRunning(running: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 09b74b2..6a2bef2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -58,6 +58,7 @@
return false
}
override fun onBackPressed() {}
+ override fun onBackProgressed(progressFraction: Float) {}
override fun setIsLaunchAnimationRunning(running: Boolean) {}
override fun setAlpha(alpha: Int, animate: Boolean) {}
override fun setAlphaChangeAnimationEndAction(r: Runnable) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 05b1ac6..6585fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -22,7 +22,6 @@
import android.view.LayoutInflater
import android.view.ViewStub
import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.keyguard.LockIconView
import com.android.systemui.R
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -96,11 +95,11 @@
?: throw IllegalStateException("Window root view could not be properly inflated")
}
- @Provides
- @SysUISingleton
// TODO(b/277762009): Do something similar to
// {@link StatusBarWindowModule.InternalWindowView} so that only
// {@link NotificationShadeWindowViewController} can inject this view.
+ @Provides
+ @SysUISingleton
fun providesNotificationShadeWindowView(
root: WindowRootView,
featureFlags: FeatureFlags,
@@ -206,21 +205,6 @@
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@Provides
@SysUISingleton
- fun providesLockIconView(
- keyguardRootView: KeyguardRootView,
- notificationPanelView: NotificationPanelView,
- featureFlags: FeatureFlags
- ): LockIconView {
- if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
- return keyguardRootView.requireViewById(R.id.lock_icon_view)
- } else {
- return notificationPanelView.requireViewById(R.id.lock_icon_view)
- }
- }
-
- // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
- @Provides
- @SysUISingleton
fun providesTapAgainView(
notificationPanelView: NotificationPanelView,
): TapAgainView {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 25bb204..f004982 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -81,6 +81,7 @@
private val powerInteractor: PowerInteractor,
) : Dumpable {
private var pulseHeight: Float = 0f
+
@get:VisibleForTesting
var fractionToShade: Float = 0f
private set
@@ -255,17 +256,17 @@
private fun updateResources() {
fullTransitionDistance = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_full_transition_distance)
+ R.dimen.lockscreen_shade_full_transition_distance)
fullTransitionDistanceByTap = context.resources.getDimensionPixelSize(
R.dimen.lockscreen_shade_transition_by_tap_distance)
notificationShelfTransitionDistance = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_notif_shelf_transition_distance)
+ R.dimen.lockscreen_shade_notif_shelf_transition_distance)
depthControllerTransitionDistance = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_depth_controller_transition_distance)
+ R.dimen.lockscreen_shade_depth_controller_transition_distance)
udfpsTransitionDistance = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_udfps_keyguard_transition_distance)
+ R.dimen.lockscreen_shade_udfps_keyguard_transition_distance)
statusBarTransitionDistance = context.resources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_status_bar_transition_distance)
+ R.dimen.lockscreen_shade_status_bar_transition_distance)
useSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
}
@@ -292,8 +293,8 @@
*/
internal fun canDragDown(): Boolean {
return (statusBarStateController.state == StatusBarState.KEYGUARD ||
- nsslController.isInLockedDownShade()) &&
- (qS.isFullyCollapsed || useSplitShade)
+ nsslController.isInLockedDownShade()) &&
+ (qS.isFullyCollapsed || useSplitShade)
}
/**
@@ -308,10 +309,15 @@
if (nsslController.isInLockedDownShade()) {
logger.logDraggedDownLockDownShade(startingChild)
statusBarStateController.setLeaveOpenOnKeyguardHide(true)
- activityStarter.dismissKeyguardThenExecute(OnDismissAction {
- nextHideKeyguardNeedsNoAnimation = true
- false
- }, cancelRunnable, false /* afterKeyguardGone */)
+ activityStarter.dismissKeyguardThenExecute(
+ {
+ nextHideKeyguardNeedsNoAnimation = true
+ false
+ },
+ cancelRunnable,
+ /* afterKeyguardGone= */
+ false,
+ )
} else {
logger.logDraggedDown(startingChild, dragLengthY)
if (!ambientState.isDozing() || startingChild != null) {
@@ -320,11 +326,18 @@
val animationHandler = { delay: Long ->
if (startingChild is ExpandableNotificationRow) {
startingChild.onExpandedByGesture(
- true /* drag down is always an open */)
+ true /* drag down is always an open */
+ )
}
shadeViewController.transitionToExpandedShade(delay)
- callbacks.forEach { it.setTransitionToFullShadeAmount(0f,
- true /* animated */, delay) }
+ callbacks.forEach {
+ it.setTransitionToFullShadeAmount(
+ 0f,
+ /* animated= */
+ true,
+ delay
+ )
+ }
// Let's reset ourselves, ready for the next animation
@@ -350,7 +363,12 @@
*/
internal fun onDragDownReset() {
logger.logDragDownAborted()
- nsslController.setDimmed(true /* dimmed */, true /* animated */)
+ nsslController.setDimmed(
+ /* dimmed= */
+ true,
+ /* animate= */
+ true,
+ )
nsslController.resetScrollPosition()
nsslController.resetCheckSnoozeLeavebehind()
setDragDownAmountAnimated(0f)
@@ -361,7 +379,12 @@
* @param above whether they dragged above it
*/
internal fun onCrossedThreshold(above: Boolean) {
- nsslController.setDimmed(!above /* dimmed */, true /* animate */)
+ nsslController.setDimmed(
+ /* dimmed= */
+ !above,
+ /* animate= */
+ true,
+ )
}
/**
@@ -411,8 +434,8 @@
*/
internal val isDragDownAnywhereEnabled: Boolean
get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD &&
- !keyguardBypassController.bypassEnabled &&
- (qS.isFullyCollapsed || useSplitShade))
+ !keyguardBypassController.bypassEnabled &&
+ (qS.isFullyCollapsed || useSplitShade))
/**
* The amount in pixels that the user has dragged down.
@@ -429,8 +452,15 @@
qsTransitionController.dragDownAmount = value
- callbacks.forEach { it.setTransitionToFullShadeAmount(field,
- false /* animate */, 0 /* delay */) }
+ callbacks.forEach {
+ it.setTransitionToFullShadeAmount(
+ field,
+ /* animate= */
+ false,
+ /* delay= */
+ 0,
+ )
+ }
mediaHierarchyManager.setTransitionToFullShadeAmount(field)
scrimTransitionController.dragDownAmount = value
@@ -538,8 +568,11 @@
shadeViewController.transitionToExpandedShade(delay)
}
}
- goToLockedShadeInternal(expandedView, animationHandler,
- cancelAction = null)
+ goToLockedShadeInternal(
+ expandedView,
+ animationHandler,
+ cancelAction = null
+ )
}
}
@@ -570,14 +603,19 @@
var entry: NotificationEntry? = null
if (expandView is ExpandableNotificationRow) {
entry = expandView.entry
- entry.setUserExpanded(true /* userExpanded */, true /* allowChildExpansion */)
+ entry.setUserExpanded(
+ /* userExpanded= */
+ true,
+ /* allowChildExpansion= */
+ true,
+ )
// Indicate that the group expansion is changing at this time -- this way the group
// and children backgrounds / divider animations will look correct.
entry.setGroupExpansionChanging(true)
userId = entry.sbn.userId
}
var fullShadeNeedsBouncer = (
- !lockScreenUserManager.shouldShowLockscreenNotifications() ||
+ !lockScreenUserManager.shouldShowLockscreenNotifications() ||
falsingCollector.shouldEnforceBouncer())
if (keyguardBypassController.bypassEnabled) {
fullShadeNeedsBouncer = false
@@ -596,7 +634,10 @@
val cancelHandler = Runnable {
draggedDownEntry?.apply {
setUserLocked(false)
- notifyHeightChanged(false /* needsAnimation */)
+ notifyHeightChanged(
+ /* needsAnimation= */
+ false,
+ )
draggedDownEntry = null
}
cancelAction?.run()
@@ -614,7 +655,10 @@
// This call needs to be after updating the shade state since otherwise
// the scrimstate resets too early
if (animationHandler != null) {
- animationHandler.invoke(0 /* delay */)
+ animationHandler.invoke(
+ /* delay= */
+ 0,
+ )
} else {
performDefaultGoToFullShadeAnimation(0)
}
@@ -721,12 +765,13 @@
it.println("isDragDownAnywhereEnabled: $isDragDownAnywhereEnabled")
it.println("isFalsingCheckNeeded: $isFalsingCheckNeeded")
it.println("isWakingToShadeLocked: $isWakingToShadeLocked")
- it.println("hasPendingHandlerOnKeyguardDismiss: " +
- "${animationHandlerOnKeyguardDismiss != null}")
+ it.println(
+ "hasPendingHandlerOnKeyguardDismiss: " +
+ "${animationHandlerOnKeyguardDismiss != null}"
+ )
}
}
-
fun addCallback(callback: Callback) {
if (!callbacks.contains(callback)) {
callbacks.add(callback)
@@ -791,7 +836,7 @@
fun updateResources(context: Context) {
minDragDistance = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_drag_down_min_distance)
+ R.dimen.keyguard_drag_down_min_distance)
val configuration = ViewConfiguration.get(context)
touchSlop = configuration.scaledTouchSlop.toFloat()
slopMultiplier = configuration.scaledAmbiguousGestureMultiplier
@@ -808,16 +853,17 @@
initialTouchY = y
initialTouchX = x
}
+
MotionEvent.ACTION_MOVE -> {
val h = y - initialTouchY
// Adjust the touch slop if another gesture may be being performed.
val touchSlop = if (event.classification
- == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE)
+ == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
touchSlop * slopMultiplier
- else
+ } else {
touchSlop
+ }
if (h > touchSlop && h > Math.abs(x - initialTouchX)) {
- falsingCollector.onNotificationStartDraggingDown()
isDraggingDown = true
captureStartingChild(initialTouchX, initialTouchY)
initialTouchY = y
@@ -858,8 +904,9 @@
}
return true
}
+
MotionEvent.ACTION_UP -> if (!falsingManager.isUnlockingDisabled && !isFalseTouch &&
- dragDownCallback.canDragDown()) {
+ dragDownCallback.canDragDown()) {
dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt())
if (startingChild != null) {
expandCallback.setUserLockedChild(startingChild, false)
@@ -870,6 +917,7 @@
stopDragging()
return false
}
+
MotionEvent.ACTION_CANCEL -> {
stopDragging()
return false
@@ -913,8 +961,8 @@
@VisibleForTesting
fun cancelChildExpansion(
- child: ExpandableView,
- animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
+ child: ExpandableView,
+ animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
) {
if (child.actualHeight == child.collapsedHeight) {
expandCallback.setUserLockedChild(child, false)
@@ -936,7 +984,6 @@
}
private fun stopDragging() {
- falsingCollector.onNotificationStopDraggingDown()
if (startingChild != null) {
cancelChildExpansion(startingChild!!)
startingChild = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 763400b..5bd40b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -65,7 +65,6 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -74,6 +73,8 @@
import com.android.systemui.util.Utils;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -87,8 +88,6 @@
import java.util.Set;
import java.util.stream.Collectors;
-import dagger.Lazy;
-
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
* notification, which this class keeps track of.
@@ -133,7 +132,6 @@
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
- private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final MediaArtworkProcessor mMediaArtworkProcessor;
private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
@@ -186,7 +184,6 @@
*/
public NotificationMediaManager(
Context context,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -205,8 +202,6 @@
mMediaArtworkProcessor = mediaArtworkProcessor;
mKeyguardBypassController = keyguardBypassController;
mMediaListeners = new ArrayList<>();
- // TODO: use KeyguardStateController#isOccluded to remove this dependency
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mNotificationShadeWindowController = notificationShadeWindowController;
mVisibilityProvider = visibilityProvider;
mMainExecutor = mainExecutor;
@@ -619,9 +614,7 @@
NotificationShadeWindowController windowController =
mNotificationShadeWindowController.get();
- boolean hideBecauseOccluded =
- mCentralSurfacesOptionalLazy.get()
- .map(CentralSurfaces::isOccluded).orElse(false);
+ boolean hideBecauseOccluded = mKeyguardStateController.isOccluded();
final boolean hasArtwork = artworkDrawable != null;
mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index fc6eaa8..2c0741e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -28,10 +28,10 @@
import android.view.VelocityTracker
import android.view.ViewConfiguration
import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
-import com.android.app.animation.Interpolators
import com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
@@ -76,6 +76,7 @@
companion object {
private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
}
+
private val mPowerManager: PowerManager?
private var mInitialTouchX: Float = 0.0f
@@ -94,7 +95,10 @@
pulseExpandAbortListener?.run()
}
}
- headsUpManager.unpinAll(true /* userUnPinned */)
+ headsUpManager.unpinAll(
+ /*userUnPinned= */
+ true,
+ )
}
}
var leavingLockscreen: Boolean = false
@@ -168,7 +172,6 @@
MotionEvent.ACTION_MOVE -> {
val h = y - mInitialTouchY
if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) {
- falsingCollector.onStartExpandingFromPulse()
isExpanding = true
captureStartingChild(mInitialTouchX, mInitialTouchY)
mInitialTouchY = y
@@ -200,14 +203,14 @@
event.action == MotionEvent.ACTION_UP) && isExpanding
val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true ||
- bypassController.canBypass()
+ bypassController.canBypass()
if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) {
// We allow cancellations/finishing to still go through here to clean up the state
return false
}
if (velocityTracker == null || !isExpanding ||
- event.actionMasked == MotionEvent.ACTION_DOWN) {
+ event.actionMasked == MotionEvent.ACTION_DOWN) {
return startExpansion(event)
}
velocityTracker!!.addMovement(event)
@@ -217,9 +220,12 @@
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
MotionEvent.ACTION_UP -> {
- velocityTracker!!.computeCurrentVelocity(1000 /* units */)
+ velocityTracker!!.computeCurrentVelocity(
+ /* units= */
+ 1000,
+ )
val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
- statusBarStateController.state != StatusBarState.SHADE
+ statusBarStateController.state != StatusBarState.SHADE
if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
finishExpansion()
} else {
@@ -227,6 +233,7 @@
}
recycleVelocityTracker()
}
+
MotionEvent.ACTION_CANCEL -> {
cancelExpansion()
recycleVelocityTracker()
@@ -243,17 +250,25 @@
}
if (statusBarStateController.isDozing) {
wakeUpCoordinator.willWakeUp = true
- mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
- "com.android.systemui:PULSEDRAG")
+ mPowerManager!!.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_GESTURE,
+ "com.android.systemui:PULSEDRAG"
+ )
}
- lockscreenShadeTransitionController.goToLockedShade(startingChild,
- needsQSAnimation = false)
+ lockscreenShadeTransitionController.goToLockedShade(
+ startingChild,
+ needsQSAnimation = false
+ )
lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false)
leavingLockscreen = true
isExpanding = false
if (mStartingChild is ExpandableNotificationRow) {
val row = mStartingChild as ExpandableNotificationRow?
- row!!.onExpandedByGesture(true /* userExpanded */)
+ row!!.onExpandedByGesture(
+ /*userExpanded= */
+ true,
+ )
}
}
@@ -261,15 +276,20 @@
var expansionHeight = max(height, 0.0f)
if (mStartingChild != null) {
val child = mStartingChild!!
- val newHeight = Math.min((child.collapsedHeight + expansionHeight).toInt(),
- child.maxContentHeight)
+ val newHeight = Math.min(
+ (child.collapsedHeight + expansionHeight).toInt(),
+ child.maxContentHeight
+ )
child.actualHeight = newHeight
} else {
wakeUpCoordinator.setNotificationsVisibleForExpansion(
height
> lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
- true /* animate */,
- true /* increaseSpeed */)
+ /*animate= */
+ true,
+ /*increaseSpeed= */
+ true
+ )
}
lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false)
}
@@ -285,8 +305,8 @@
@VisibleForTesting
fun reset(
- child: ExpandableView,
- animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+ child: ExpandableView,
+ animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
) {
if (child.actualHeight == child.collapsedHeight) {
setUserLocked(child, false)
@@ -315,15 +335,19 @@
private fun cancelExpansion() {
isExpanding = false
- falsingCollector.onExpansionFromPulseStopped()
if (mStartingChild != null) {
reset(mStartingChild!!)
mStartingChild = null
}
lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true)
- wakeUpCoordinator.setNotificationsVisibleForExpansion(false /* visible */,
- true /* animate */,
- false /* increaseSpeed */)
+ wakeUpCoordinator.setNotificationsVisibleForExpansion(
+ /*visible= */
+ false,
+ /*animate= */
+ true,
+ /*increaseSpeed= */
+ false
+ )
}
private fun findView(x: Float, y: Float): ExpandableView? {
@@ -335,7 +359,9 @@
val childAtRawPosition = stackScrollerController.getChildAtRawPosition(totalX, totalY)
return if (childAtRawPosition != null && childAtRawPosition.isContentExpandable) {
childAtRawPosition
- } else null
+ } else {
+ null
+ }
}
fun setUp(stackScrollerController: NotificationStackScrollLayoutController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 2ad71e7..80274bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.window.StatusBarWindowController
import java.lang.IllegalStateException
import javax.inject.Inject
+import javax.inject.Provider
/**
* Responsible for creating the status bar window and initializing the root components of that
@@ -35,6 +36,7 @@
@SysUISingleton
class StatusBarInitializer @Inject constructor(
private val windowController: StatusBarWindowController,
+ private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
) {
@@ -43,11 +45,9 @@
/**
* Creates the status bar window and root views, and initializes the component.
*
- * TODO(b/277762009): Inject StatusBarFragmentCreator and make this class a CoreStartable.
+ * TODO(b/277764509): Initialize the status bar via [CoreStartable#start].
*/
- fun initializeStatusBar(
- statusBarFragmentCreator: () -> CollapsedStatusBarFragment,
- ) {
+ fun initializeStatusBar() {
windowController.fragmentHostManager.addTagListener(
CollapsedStatusBarFragment.TAG,
object : FragmentHostManager.FragmentListener {
@@ -67,11 +67,14 @@
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
// nop
}
- }).fragmentManager
+ }
+ ).fragmentManager
.beginTransaction()
- .replace(R.id.status_bar_container,
- statusBarFragmentCreator.invoke(),
- CollapsedStatusBarFragment.TAG)
+ .replace(
+ R.id.status_bar_container,
+ collapsedStatusBarFragmentProvider.get(),
+ CollapsedStatusBarFragment.TAG
+ )
.commit()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f726c4e..3dfe068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -64,7 +64,6 @@
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -135,7 +134,6 @@
@Provides
static NotificationMediaManager provideNotificationMediaManager(
Context context,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
MediaArtworkProcessor mediaArtworkProcessor,
@@ -152,7 +150,6 @@
DisplayManager displayManager) {
return new NotificationMediaManager(
context,
- centralSurfacesOptionalLazy,
notificationShadeWindowController,
visibilityProvider,
mediaArtworkProcessor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index f40f570..a3bc002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -132,8 +132,9 @@
override fun onStatusEvent(event: StatusEvent) {
Assert.isMainThread()
- // Ignore any updates until the system is up and running
- if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ // Ignore any updates until the system is up and running. However, for important events that
+ // request to be force visible (like privacy), ignore whether it's too early.
+ if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
index 5fa83ef..6b5a548 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -93,8 +93,9 @@
@SystemAnimationState override fun getAnimationState() = animationState
override fun onStatusEvent(event: StatusEvent) {
- // Ignore any updates until the system is up and running
- if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+ // Ignore any updates until the system is up and running. However, for important events that
+ // request to be force visible (like privacy), ignore whether it's too early.
+ if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 637637d..09be41b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -19,12 +19,13 @@
import android.content.Context;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.shade.ShadeEventsModule;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -76,10 +77,13 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
+import com.android.systemui.util.kotlin.JavaAdapter;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
import java.util.concurrent.Executor;
@@ -110,6 +114,13 @@
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Binds {@link NotificationGutsManager} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationGutsManager.class)
+ CoreStartable bindsNotificationGutsManager(NotificationGutsManager notificationGutsManager);
+
+
/** Provides an instance of {@link VisibilityLocationProvider} */
@Binds
VisibilityLocationProvider bindVisibilityLocationProvider(
@@ -125,7 +136,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
NotificationLogger.ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
return new NotificationLogger(
@@ -135,11 +147,18 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
- shadeExpansionStateManager,
+ windowRootViewVisibilityInteractor,
+ javaAdapter,
expansionStateLogger,
notificationPanelLogger);
}
+ /** Binds {@link NotificationLogger} as a {@link CoreStartable}. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationLogger.class)
+ CoreStartable bindsNotificationLogger(NotificationLogger notificationLogger);
+
/** Provides an instance of {@link NotificationPanelLogger} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 26f97de..8d2a63e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -33,10 +33,11 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -48,6 +49,7 @@
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.util.Compile;
+import com.android.systemui.util.kotlin.JavaAdapter;
import java.util.Collection;
import java.util.Collections;
@@ -62,7 +64,7 @@
* Handles notification logging, in particular, logging which notifications are visible and which
* are not.
*/
-public class NotificationLogger implements StateListener {
+public class NotificationLogger implements StateListener, CoreStartable {
static final String TAG = "NotificationLogger";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
@@ -81,6 +83,8 @@
private final NotifPipeline mNotifPipeline;
private final NotificationPanelLogger mNotificationPanelLogger;
private final ExpansionStateLogger mExpansionStateLogger;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+ private final JavaAdapter mJavaAdapter;
protected Handler mHandler = new Handler();
protected IStatusBarService mBarService;
@@ -88,10 +92,7 @@
private NotificationListContainer mListContainer;
private final Object mDozingLock = new Object();
@GuardedBy("mDozingLock")
- private Boolean mDozing = null; // Use null to indicate state is not yet known
- @GuardedBy("mDozingLock")
private Boolean mLockscreen = null; // Use null to indicate state is not yet known
- private Boolean mPanelExpanded = null; // Use null to indicate state is not yet known
private boolean mLogging = false;
// Tracks notifications currently visible in mNotificationStackScroller and
@@ -202,7 +203,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
ExpansionStateLogger expansionStateLogger,
NotificationPanelLogger notificationPanelLogger) {
mNotificationListener = notificationListener;
@@ -214,9 +216,10 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mExpansionStateLogger = expansionStateLogger;
mNotificationPanelLogger = notificationPanelLogger;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
+ mJavaAdapter = javaAdapter;
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
- shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged);
registerNewPipelineListener();
}
@@ -239,6 +242,22 @@
mListContainer = listContainer;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisibleAndInteractive(),
+ this::onLockscreenOrShadeVisibleAndInteractiveChanged);
+ }
+
+ private void onLockscreenOrShadeVisibleAndInteractiveChanged(boolean visible) {
+ if (visible) {
+ startNotificationLogging();
+ } else {
+ // Ensure we stop notification logging when the device isn't interactive.
+ stopNotificationLogging();
+ }
+ }
+
public void stopNotificationLogging() {
if (mLogging) {
mLogging = false;
@@ -263,10 +282,15 @@
if (DEBUG) {
Log.i(TAG, "startNotificationLogging");
}
+ boolean lockscreen;
+ synchronized (mDozingLock) {
+ lockscreen = mLockscreen != null && mLockscreen;
+ }
+ mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
- // Some transitions like mVisibleToUser=false -> mVisibleToUser=true don't
- // cause the scroller to emit child location events. Hence generate
- // one ourselves to guarantee that we're reporting visible
+ // Sometimes, the transition from lockscreenOrShadeVisible=false ->
+ // lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
+ // events. Hence generate one ourselves to guarantee that we're reporting visible
// notifications.
// (Note that in cases where the scroller does emit events, this
// additional event doesn't break anything.)
@@ -274,13 +298,6 @@
}
}
- private void setDozing(boolean dozing) {
- synchronized (mDozingLock) {
- mDozing = dozing;
- maybeUpdateLoggingStatus();
- }
- }
-
private void logNotificationVisibilityChanges(
Collection<NotificationVisibility> newlyVisible,
Collection<NotificationVisibility> noLongerVisible) {
@@ -362,39 +379,6 @@
}
}
- @Override
- public void onDozingChanged(boolean isDozing) {
- if (DEBUG) {
- Log.i(TAG, "onDozingChanged: new=" + isDozing);
- }
- setDozing(isDozing);
- }
-
- @GuardedBy("mDozingLock")
- private void maybeUpdateLoggingStatus() {
- if (mPanelExpanded == null || mDozing == null) {
- if (DEBUG) {
- Log.i(TAG, "Panel status unclear: panelExpandedKnown="
- + (mPanelExpanded == null) + " dozingKnown=" + (mDozing == null));
- }
- return;
- }
- // Once we know panelExpanded and Dozing, turn logging on & off when appropriate
- boolean lockscreen = mLockscreen == null ? false : mLockscreen;
- if (mPanelExpanded && !mDozing) {
- mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
- if (DEBUG) {
- Log.i(TAG, "Notification panel shown, lockscreen=" + lockscreen);
- }
- startNotificationLogging();
- } else {
- if (DEBUG) {
- Log.i(TAG, "Notification panel hidden, lockscreen=" + lockscreen);
- }
- stopNotificationLogging();
- }
- }
-
/**
* Called when the notification is expanded / collapsed.
*/
@@ -404,20 +388,6 @@
}
@VisibleForTesting
- void onShadeExpansionFullyChanged(Boolean isExpanded) {
- // mPanelExpanded is initialized as null
- if (mPanelExpanded == null || !mPanelExpanded.equals(isExpanded)) {
- if (DEBUG) {
- Log.i(TAG, "onPanelExpandedChanged: new=" + isExpanded);
- }
- mPanelExpanded = isExpanded;
- synchronized (mDozingLock) {
- maybeUpdateLoggingStatus();
- }
- }
- }
-
- @VisibleForTesting
void onChildLocationsChanged() {
if (mHandler.hasCallbacks(mVisibilityReporter)) {
// Visibilities will be reported when the existing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d92d11b..c02382d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -74,7 +74,6 @@
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.ViewRefactorFlag;
@@ -245,7 +244,6 @@
private NotificationEntry mEntry;
private String mAppName;
private FalsingManager mFalsingManager;
- private FalsingCollector mFalsingCollector;
/**
* Whether or not the notification is using the heads up view and should peek from the top.
@@ -1711,7 +1709,6 @@
OnExpandClickListener onExpandClickListener,
CoordinateOnClickListener onFeedbackClickListener,
FalsingManager falsingManager,
- FalsingCollector falsingCollector,
StatusBarStateController statusBarStateController,
PeopleNotificationIdentifier peopleNotificationIdentifier,
OnUserInteractionCallback onUserInteractionCallback,
@@ -1743,7 +1740,6 @@
mOnExpandClickListener = onExpandClickListener;
setOnFeedbackClickListener(onFeedbackClickListener);
mFalsingManager = falsingManager;
- mFalsingCollector = falsingCollector;
mStatusBarStateController = statusBarStateController;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
for (NotificationContentView l : mLayouts) {
@@ -2471,7 +2467,6 @@
* @param allowChildExpansion whether a call to this method allows expanding children
*/
public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
- mFalsingCollector.setNotificationExpanded();
if (mIsSummaryWithChildren && !shouldShowPublic() && allowChildExpansion
&& !mChildrenContainer.showingAsLowPriority()) {
final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 80f5d19..af55f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -34,7 +34,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
@@ -102,7 +101,6 @@
private final NotificationGutsManager mNotificationGutsManager;
private final OnUserInteractionCallback mOnUserInteractionCallback;
private final FalsingManager mFalsingManager;
- private final FalsingCollector mFalsingCollector;
private final FeatureFlags mFeatureFlags;
private final boolean mAllowLongPress;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@@ -222,7 +220,6 @@
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
OnUserInteractionCallback onUserInteractionCallback,
FalsingManager falsingManager,
- FalsingCollector falsingCollector,
FeatureFlags featureFlags,
PeopleNotificationIdentifier peopleNotificationIdentifier,
Optional<BubblesManager> bubblesManagerOptional,
@@ -251,7 +248,6 @@
mFalsingManager = falsingManager;
mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
mAllowLongPress = allowLongPress;
- mFalsingCollector = falsingCollector;
mFeatureFlags = featureFlags;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesManagerOptional = bubblesManagerOptional;
@@ -285,7 +281,6 @@
mOnExpandClickListener,
mOnFeedbackClickListener,
mFalsingManager,
- mFalsingCollector,
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f79ea8..44ead26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -45,6 +45,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.notification.ConversationIconFactory;
+import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -53,6 +54,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -69,6 +71,7 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.wmshell.BubblesManager;
import java.util.Optional;
@@ -80,7 +83,7 @@
* closing guts, and keeping track of the currently exposed notification guts.
*/
@SysUISingleton
-public class NotificationGutsManager implements NotifGutsViewManager {
+public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable {
private static final String TAG = "NotificationGutsManager";
// Must match constant in Settings. Used to highlight preferences when linking to Settings.
@@ -109,6 +112,7 @@
private final Handler mMainHandler;
private final Handler mBgHandler;
+ private final JavaAdapter mJavaAdapter;
private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
@@ -121,6 +125,7 @@
private final UserContextProvider mContextTracker;
private final UiEventLogger mUiEventLogger;
private final ShadeController mShadeController;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private NotifGutsViewListener mGutsListener;
private final HeadsUpManagerPhone mHeadsUpManagerPhone;
private final ActivityStarter mActivityStarter;
@@ -129,6 +134,7 @@
public NotificationGutsManager(Context context,
@Main Handler mainHandler,
@Background Handler bgHandler,
+ JavaAdapter javaAdapter,
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
@@ -143,6 +149,7 @@
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback,
ShadeController shadeController,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
NotificationLockscreenUserManager notificationLockscreenUserManager,
StatusBarStateController statusBarStateController,
DeviceProvisionedController deviceProvisionedController,
@@ -152,6 +159,7 @@
mContext = context;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
+ mJavaAdapter = javaAdapter;
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
@@ -166,6 +174,7 @@
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
mShadeController = shadeController;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mLockscreenUserManager = notificationLockscreenUserManager;
mStatusBarStateController = statusBarStateController;
mDeviceProvisionedController = deviceProvisionedController;
@@ -187,6 +196,25 @@
mNotificationActivityStarter = notificationActivityStarter;
}
+ @Override
+ public void start() {
+ mJavaAdapter.alwaysCollectFlow(
+ mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(),
+ this::onLockscreenShadeVisibilityChanged);
+ }
+
+ private void onLockscreenShadeVisibilityChanged(boolean visible) {
+ if (!visible) {
+ closeAndSaveGuts(
+ /* removeLeavebehind= */ true ,
+ /* force= */ true,
+ /* removeControls= */ true,
+ /* x= */ -1,
+ /* y= */ -1,
+ /* resetMenu= */ true);
+ }
+ }
+
public void onDensityOrFontScaleChanged(NotificationEntry entry) {
setExposedGuts(entry.getGuts());
bindGuts(entry.getRow());
@@ -512,7 +540,7 @@
mNotificationGutsExposed.removeCallbacks(mOpenRunnable);
mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
}
- if (resetMenu) {
+ if (resetMenu && mListContainer != null) {
mListContainer.resetExposedMenuView(false /* animate */, true /* force */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6db8df9..d8f513c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -455,7 +455,6 @@
@Override
public void onDragCancelled(View v) {
- mFalsingCollector.onNotificationStopDismissing();
}
/**
@@ -501,7 +500,6 @@
}
mView.addSwipedOutView(view);
- mFalsingCollector.onNotificationDismissed();
if (mFalsingCollector.shouldEnforceBouncer()) {
mActivityStarter.executeRunnableDismissingKeyguard(
null,
@@ -549,7 +547,6 @@
@Override
public void onBeginDrag(View v) {
- mFalsingCollector.onNotificationStartDismissing();
mView.onSwipeBegin(v);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index d0a093c..0ff1a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -200,12 +200,6 @@
void onKeyguardViewManagerStatesUpdated();
- boolean isPulsing();
-
- boolean isOccluded();
-
- boolean isDeviceInVrMode();
-
NotificationPresenter getPresenter();
/**
@@ -247,8 +241,6 @@
@Deprecated
float getDisplayHeight();
- void readyForKeyguardDone();
-
void showKeyguard();
boolean hideKeyguard();
@@ -370,8 +362,6 @@
@Deprecated
float getDisplayDensity();
- void extendDozePulse();
-
public static class KeyboardShortcutsMessage {
final int mDeviceId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 72a5cfe..37038a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -41,9 +41,6 @@
override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
override fun isLaunchingActivityOverLockscreen() = false
override fun onKeyguardViewManagerStatesUpdated() {}
- override fun isPulsing() = false
- override fun isOccluded() = false
- override fun isDeviceInVrMode() = false
override fun getPresenter(): NotificationPresenter? = null
override fun onInputFocusTransfer(start: Boolean, cancel: Boolean, velocity: Float) {}
override fun getCommandQueuePanelsEnabled() = false
@@ -55,7 +52,6 @@
override fun dump(pwOriginal: PrintWriter, args: Array<String>) {}
override fun getDisplayWidth() = 0f
override fun getDisplayHeight() = 0f
- override fun readyForKeyguardDone() {}
override fun showKeyguard() {}
override fun hideKeyguard() = false
override fun showKeyguardImpl() {}
@@ -117,7 +113,6 @@
override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
override fun getQSPanelController(): QSPanelController? = null
override fun getDisplayDensity() = 0f
- override fun extendDozePulse() {}
override fun setIsLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen: Boolean) {}
override fun getAnimatorControllerFromNotification(
associatedView: ExpandableNotificationRow?,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 4aa8b6b..b32ccbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -92,17 +92,12 @@
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
-import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.DateTimeView;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
@@ -176,6 +171,7 @@
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -221,15 +217,12 @@
import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -459,9 +452,9 @@
mNotificationShadeWindowViewControllerLazy;
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
private final PluginManager mPluginManager;
private final ShadeController mShadeController;
+ private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final InitController mInitController;
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -489,13 +482,11 @@
private View mReportRejectedTouch;
private final NotificationGutsManager mGutsManager;
- private final NotificationLogger mNotificationLogger;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final KeyguardViewMediator mKeyguardViewMediator;
protected final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final BrightnessSliderController.Factory mBrightnessSliderFactory;
private final FeatureFlags mFeatureFlags;
- private final boolean mAnimateBack;
private final FragmentService mFragmentService;
private final ScreenOffAnimationController mScreenOffAnimationController;
private final WallpaperController mWallpaperController;
@@ -506,15 +497,6 @@
private final Provider<FingerprintManager> mFingerprintManager;
private final ActivityStarter mActivityStarter;
- private CentralSurfacesComponent mCentralSurfacesComponent;
-
- /**
- * This keeps track of whether we have (or haven't) registered the predictive back callback.
- * Since we can have visible -> visible transitions, we need to avoid
- * double-registering (or double-unregistering) our callback.
- */
- private boolean mIsBackCallbackRegistered = false;
-
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
private @Appearance int mAppearance;
@@ -640,31 +622,6 @@
private final InteractionJankMonitor mJankMonitor;
- /** Existing callback that handles back gesture invoked for the Shade. */
- private final OnBackInvokedCallback mOnBackInvokedCallback;
-
- /**
- * New callback that handles back gesture invoked, cancel, progress
- * and provides feedback via Shade animation.
- * (enabled via the WM_SHADE_ANIMATE_BACK_GESTURE flag)
- */
- private final OnBackAnimationCallback mOnBackAnimationCallback = new OnBackAnimationCallback() {
- @Override
- public void onBackInvoked() {
- mBackActionInteractor.onBackRequested();
- }
-
- @Override
- public void onBackProgressed(BackEvent event) {
- if (mBackActionInteractor.shouldBackBeHandled()) {
- if (mShadeSurface.canBeCollapsed()) {
- float fraction = event.getProgress();
- mShadeSurface.onBackProgressed(fraction);
- }
- }
- }
- };
-
/**
* Public constructor for CentralSurfaces.
*
@@ -694,7 +651,6 @@
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
NotificationGutsManager notificationGutsManager,
- NotificationLogger notificationLogger,
NotificationInterruptStateProvider notificationInterruptStateProvider,
ShadeExpansionStateManager shadeExpansionStateManager,
KeyguardViewMediator keyguardViewMediator,
@@ -741,10 +697,10 @@
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
- CentralSurfacesComponent.Factory centralSurfacesComponentFactory,
Lazy<CentralSurfacesCommandQueueCallbacks> commandQueueCallbacksLazy,
PluginManager pluginManager,
ShadeController shadeController,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@@ -803,7 +759,6 @@
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
mGutsManager = notificationGutsManager;
- mNotificationLogger = notificationLogger;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
mShadeExpansionStateManager = shadeExpansionStateManager;
mKeyguardViewMediator = keyguardViewMediator;
@@ -852,10 +807,10 @@
mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
mVolumeComponent = volumeComponent;
mCommandQueue = commandQueue;
- mCentralSurfacesComponentFactory = centralSurfacesComponentFactory;
mCommandQueueCallbacksLazy = commandQueueCallbacksLazy;
mPluginManager = pluginManager;
mShadeController = shadeController;
+ mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
@@ -926,14 +881,6 @@
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
- // Based on teamfood flag, enable predictive back animation for the Shade.
- mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
- mOnBackInvokedCallback = () -> {
- if (DEBUG) {
- Log.d(TAG, "mOnBackInvokedCallback() called");
- }
- mBackActionInteractor.onBackRequested();
- };
}
private void initBubbles(Bubbles bubbles) {
@@ -1171,7 +1118,10 @@
new FoldStateListener(mContext, this::onFoldedStateChanged));
}
- @VisibleForTesting
+ /**
+ * @deprecated use {@link
+ * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+ */ @VisibleForTesting
void initShadeVisibilityListener() {
mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
@Override
@@ -1236,10 +1186,15 @@
updateResources();
updateTheme();
- inflateStatusBarWindow();
+ setUpShade();
getNotificationShadeWindowView().setOnTouchListener(getStatusBarWindowTouchListener());
mWallpaperController.setRootView(getNotificationShadeWindowView());
+ mDemoModeController.addCallback(mDemoModeCallback);
+
+ mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
+ mCommandQueue.addCallback(mCommandQueueCallbacks);
+
// TODO: Deal with the ugliness that comes from having some of the status bar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
@@ -1270,8 +1225,7 @@
setBouncerShowingForStatusBarComponents(mBouncerShowing);
checkBarModes();
});
- mStatusBarInitializer.initializeStatusBar(
- mCentralSurfacesComponent::createCollapsedStatusBarFragment);
+ mStatusBarInitializer.initializeStatusBar();
mStatusBarTouchableRegionManager.setup(this, getNotificationShadeWindowView());
@@ -1480,7 +1434,7 @@
// - Shade is in QQS over keyguard - swiping up should take us back to keyguard
if (!isKeyguardShowing()
|| mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
- || isOccluded()
+ || mKeyguardStateController.isOccluded()
|| !mKeyguardStateController.canDismissLockScreen()
|| mKeyguardViewMediator.isAnySimPinSecure()
|| (mQsController.getExpanded() && trackingTouch)
@@ -1563,6 +1517,7 @@
mNotifListContainer,
mStackScrollerController.getNotifStackController(),
mNotificationActivityStarter);
+ mWindowRootViewVisibilityInteractor.setUp(mPresenter, mNotificationsController);
}
/**
@@ -1593,15 +1548,7 @@
};
}
- private void inflateStatusBarWindow() {
- if (mCentralSurfacesComponent != null) {
- Log.e(TAG, "CentralSurfacesComponent being recreated; this is unexpected.");
- }
- mCentralSurfacesComponent = mCentralSurfacesComponentFactory.create();
- mFragmentService.addFragmentInstantiationProvider(
- CollapsedStatusBarFragment.class,
- mCentralSurfacesComponent::createCollapsedStatusBarFragment);
-
+ private void setUpShade() {
// Ideally, NotificationShadeWindowController could automatically fetch the window root view
// in #attach or a CoreStartable.start method or something similar. But for now, to avoid
// regressions, we'll continue standing up the root view in CentralSurfaces.
@@ -1610,12 +1557,6 @@
mShadeController.setNotificationShadeWindowViewController(
getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
-
- // Listen for demo mode changes
- mDemoModeController.addCallback(mDemoModeCallback);
-
- mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
- mCommandQueue.addCallback(mCommandQueueCallbacks);
}
protected NotificationShadeWindowViewController getNotificationShadeWindowViewController() {
@@ -1709,27 +1650,6 @@
}
@Override
- public boolean isPulsing() {
- return mDozeServiceHost.isPulsing();
- }
-
- /**
- * When the keyguard is showing and covered by a "showWhenLocked" activity it
- * is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager}
- *
- * @return whether the keyguard is currently occluded
- */
- @Override
- public boolean isOccluded() {
- return mKeyguardStateController.isOccluded();
- }
-
- @Override
- public boolean isDeviceInVrMode() {
- return mPresenter.isDeviceInVrMode();
- }
-
- @Override
public NotificationPresenter getPresenter() {
return mPresenter;
}
@@ -1963,8 +1883,6 @@
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
- pw.println(" ShadeWindowView: ");
- getNotificationShadeWindowViewController().dump(pw, args);
CentralSurfaces.dumpBarTransitions(
pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
@@ -2077,11 +1995,6 @@
return mDisplay.getRotation();
}
- @Override
- public void readyForKeyguardDone() {
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
- }
-
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -2170,82 +2083,6 @@
com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
- protected void handleVisibleToUserChanged(boolean visibleToUser) {
- if (visibleToUser) {
- onVisibleToUser();
- mNotificationLogger.startNotificationLogging();
-
- if (!mIsBackCallbackRegistered) {
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.getOnBackInvokedDispatcher()
- .registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- mAnimateBack ? mOnBackAnimationCallback
- : mOnBackInvokedCallback);
- mIsBackCallbackRegistered = true;
- if (DEBUG) Log.d(TAG, "is now VISIBLE to user AND callback registered");
- }
- } else {
- if (DEBUG) Log.d(TAG, "is now VISIBLE to user, BUT callback ALREADY unregistered");
- }
- } else {
- mNotificationLogger.stopNotificationLogging();
- onInvisibleToUser();
-
- if (mIsBackCallbackRegistered) {
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.getOnBackInvokedDispatcher()
- .unregisterOnBackInvokedCallback(
- mAnimateBack ? mOnBackAnimationCallback
- : mOnBackInvokedCallback);
- mIsBackCallbackRegistered = false;
- if (DEBUG) Log.d(TAG, "is NOT VISIBLE to user, AND callback unregistered");
- }
- } else {
- if (DEBUG) {
- Log.d(TAG,
- "is NOT VISIBLE to user, BUT NO callback (or callback ALREADY "
- + "unregistered)");
- }
- }
- }
- }
-
- void onVisibleToUser() {
- /* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
- * this.
- */
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
- || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationsController.getActiveNotificationsCount();
- if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- final int finalNotificationLoad = notificationLoad;
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelRevealed(clearNotificationEffects,
- finalNotificationLoad);
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- }
-
- void onInvisibleToUser() {
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelHidden();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- }
-
private void logStateToEventlog() {
boolean isShowing = mKeyguardStateController.isShowing();
boolean isOccluded = mKeyguardStateController.isOccluded();
@@ -2325,11 +2162,12 @@
// there's no surface we can show to the user. Note that the device goes fully interactive
// late in the transition, so we also allow the device to start dozing once the screen has
// turned off fully.
+ boolean keyguardShowingUnOccluded =
+ mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded();
boolean keyguardForDozing = mDozeServiceHost.getDozingRequested()
&& (!mDeviceInteractive || (isGoingToSleep()
- && (isScreenFullyOff()
- || (mKeyguardStateController.isShowing() && !isOccluded()))));
- boolean isWakingAndOccluded = isOccluded() && isWakingOrAwake();
+ && (isScreenFullyOff() || keyguardShowingUnOccluded)));
+ boolean isWakingAndOccluded = mKeyguardStateController.isOccluded() && isWakingOrAwake();
boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
|| keyguardForDozing) && !wakeAndUnlocking && !isWakingAndOccluded;
if (keyguardForDozing) {
@@ -2728,11 +2566,6 @@
mNavigationBarController.showPinningEscapeToast(mDisplayId);
}
- protected ViewRootImpl getViewRootImpl() {
- View root = mNotificationShadeWindowController.getWindowRootView();
- if (root != null) return root.getViewRootImpl();
- return null;
- }
/**
* Propagation of the bouncer state, indicating that it's fully visible.
*/
@@ -2779,7 +2612,6 @@
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
- updateVisibleToUser();
updateNotificationPanelTouchState();
getNotificationShadeWindowViewController().cancelCurrentTouch();
@@ -2852,7 +2684,7 @@
// cancelling a sleep), from the power button, on a device with a power button
// FPS, and 'press to unlock' is required.
mShouldDelayWakeUpAnimation =
- !isPulsing()
+ !mDozeServiceHost.isPulsing()
&& mStatusBarStateController.getDozeAmount() == 1f
&& mWakefulnessLifecycle.getLastWakeReason()
== PowerManager.WAKE_REASON_POWER_BUTTON
@@ -2878,7 +2710,6 @@
/* wakingUp= */ true,
mShouldDelayWakeUpAnimation);
- updateVisibleToUser();
updateIsKeyguard();
mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn()
&& mFeatureFlags.isEnabled(
@@ -3130,7 +2961,7 @@
mScrimController.setExpansionAffectsAlpha(!unlocking);
if (mAlternateBouncerInteractor.isVisibleState()) {
- if ((!isOccluded() || mShadeSurface.isPanelExpanded())
+ if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded())
&& (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
|| mTransitionToFullShadeProgress > 0f)) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
@@ -3166,7 +2997,9 @@
// This will cancel the keyguardFadingAway animation if it is running. We need to do
// this as otherwise it can remain pending and leave keyguard in a weird state.
mUnlockScrimCallback.onCancelled();
- } else if (mKeyguardStateController.isShowing() && !isOccluded() && !unlocking) {
+ } else if (mKeyguardStateController.isShowing()
+ && !mKeyguardStateController.isOccluded()
+ && !unlocking) {
mScrimController.transitionTo(ScrimState.KEYGUARD);
} else if (mKeyguardStateController.isShowing() && mKeyguardUpdateMonitor.isDreaming()
&& !unlocking) {
@@ -3205,9 +3038,6 @@
protected boolean mVisible;
- // mScreenOnFromKeyguard && mVisible.
- private boolean mVisibleToUser;
-
protected DevicePolicyManager mDevicePolicyManager;
private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@@ -3331,21 +3161,8 @@
if (visible) {
DejankUtils.notifyRendererOfExpensiveFrame(
getNotificationShadeWindowView(), "onShadeVisibilityChanged");
- } else {
- mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
- updateVisibleToUser();
- }
-
- protected void updateVisibleToUser() {
- boolean oldVisibleToUser = mVisibleToUser;
- mVisibleToUser = mVisible && mDeviceInteractive;
-
- if (oldVisibleToUser != mVisibleToUser) {
- handleVisibleToUserChanged(mVisibleToUser);
- }
}
/**
@@ -3403,11 +3220,6 @@
}
}
- @Override
- public void extendDozePulse(){
- mDozeScrimController.extendPulse();
- }
-
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -3526,7 +3338,7 @@
// If we're visible and switched to SHADE_LOCKED (the user dragged
// down on the lockscreen), clear notification LED, vibration,
// ringing.
- // Other transitions are covered in handleVisibleToUserChanged().
+ // Other transitions are covered in WindowRootViewVisibilityInteractor.
if (mVisible && (newState == StatusBarState.SHADE_LOCKED
|| mStatusBarStateController.goingToFullShade())) {
clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 801cdbf..4849f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -185,7 +185,7 @@
return mDozingRequested;
}
- boolean isPulsing() {
+ public boolean isPulsing() {
return mPulsing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 62a8cfd..40432ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -763,7 +763,7 @@
* Sets the amount of vertical over scroll that should be performed on the notifications scrim.
*/
public void setNotificationsOverScrollAmount(int overScrollAmount) {
- mNotificationsScrim.setTranslationY(overScrollAmount);
+ if (mNotificationsScrim != null) mNotificationsScrim.setTranslationY(overScrollAmount);
}
/**
@@ -1480,7 +1480,7 @@
public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
// TODO: remove this. This is necessary because of an order-of-operations limitation.
- // The fix is to move more of these class into @CentralSurfacesScope
+ // The fix is to move more of these class into @SysUISingleton.
if (mScrimBehind == null) {
mScrimBehindChangeRunnable = changeRunnable;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index d5cb6b6..4878d45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -81,11 +81,7 @@
/** Removes an icon that had come from an active tile service. */
void removeIconForTile(String slot);
- /**
- * Adds or updates an icon for the given slot for **internal system icons**.
- *
- * TODO(b/265307726): Re-name this to `setInternalIcon`.
- */
+ /** Adds or updates an icon for the given slot for **internal system icons**. */
void setIcon(String slot, int resourceId, CharSequence contentDescription);
/**
@@ -127,11 +123,6 @@
*/
void removeIcon(String slot, int tag);
- /**
- * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`.
- */
- void removeAllIconsForSlot(String slot);
-
// TODO: See if we can rename this tunable name.
String ICON_HIDE_LIST = "icon_blacklist";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 553cbc5..0f4d68c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -385,13 +385,7 @@
}
private void removeAllIconsForExternalSlot(String slotName) {
- removeAllIconsForSlot(createExternalSlotName(slotName));
- }
-
- /** */
- @Override
- public void removeAllIconsForSlot(String slotName) {
- removeAllIconsForSlot(slotName, /* fromNewPipeline */ false);
+ removeAllIconsForSlot(createExternalSlotName(slotName), /* fromNewPipeline= */ false);
}
private void removeAllIconsForSlot(String slotName, boolean fromNewPipeline) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
deleted file mode 100644
index 1a04b91..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.STATUS_BAR_FRAGMENT;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import com.android.systemui.shade.ShadeHeaderController;
-import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-
-import dagger.Subcomponent;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-/**
- * Dagger subcomponent for classes (semi-)related to the status bar. The component is created once
- * inside {@link CentralSurfacesImpl} and never re-created.
- *
- * TODO(b/197137564): This should likely be re-factored a bit. It includes classes that aren't
- * directly related to status bar functionality, like multiple notification classes. And, the fact
- * that it has many getter methods indicates that we need to access many of these classes from
- * outside the component. Should more items be moved *into* this component to avoid so many getters?
- */
-@Subcomponent(modules = {
- StatusBarViewModule.class,
-})
-@CentralSurfacesComponent.CentralSurfacesScope
-public interface CentralSurfacesComponent {
- /**
- * Builder for {@link CentralSurfacesComponent}.
- */
- @Subcomponent.Factory
- interface Factory {
- CentralSurfacesComponent create();
- }
-
- /**
- * Scope annotation for singleton items within the CentralSurfacesComponent.
- */
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface CentralSurfacesScope {}
-
- /**
- * Creates a {@link ShadeHeaderController}.
- */
- ShadeHeaderController getLargeScreenShadeHeaderController();
-
- /**
- * Creates a new {@link CollapsedStatusBarFragment} each time it's called. See
- * {@link StatusBarViewModule#createCollapsedStatusBarFragment}.
- */
- @Named(STATUS_BAR_FRAGMENT)
- CollapsedStatusBarFragment createCollapsedStatusBarFragment();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
deleted file mode 100644
index ebdde78..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.OperatorNameViewController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
-import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
-import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.util.CarrierConfigTracker;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
-/**
- * A module for {@link CentralSurfacesComponent.CentralSurfacesScope} components.
- *
- * @deprecated CentralSurfacesScope will be removed shortly (b/277762009). Classes should be
- * annotated with @SysUISingleton instead.
- */
-@Module(subcomponents = StatusBarFragmentComponent.class)
-@Deprecated
-public abstract class StatusBarViewModule {
-
- public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
-
- /**
- * Creates a new {@link CollapsedStatusBarFragment}.
- *
- * **IMPORTANT**: This method intentionally does not have
- * {@link CentralSurfacesComponent.CentralSurfacesScope}, which means a new fragment *will* be
- * created each time this method is called. This is intentional because we need fragments to
- * re-created in certain lifecycle scenarios.
- *
- * This provider is {@link Named} such that it does not conflict with the provider inside of
- * {@link StatusBarFragmentComponent}.
- */
- @Provides
- @Named(STATUS_BAR_FRAGMENT)
- public static CollapsedStatusBarFragment createCollapsedStatusBarFragment(
- StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
- OngoingCallController ongoingCallController,
- SystemStatusAnimationScheduler animationScheduler,
- StatusBarLocationPublisher locationPublisher,
- NotificationIconAreaController notificationIconAreaController,
- ShadeExpansionStateManager shadeExpansionStateManager,
- FeatureFlags featureFlags,
- StatusBarIconController statusBarIconController,
- StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
- CollapsedStatusBarViewModel collapsedStatusBarViewModel,
- CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
- StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
- KeyguardStateController keyguardStateController,
- ShadeViewController shadeViewController,
- StatusBarStateController statusBarStateController,
- CommandQueue commandQueue,
- CarrierConfigTracker carrierConfigTracker,
- CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
- OperatorNameViewController.Factory operatorNameViewControllerFactory,
- SecureSettings secureSettings,
- @Main Executor mainExecutor,
- DumpManager dumpManager,
- StatusBarWindowStateController statusBarWindowStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor
- ) {
- return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
- ongoingCallController,
- animationScheduler,
- locationPublisher,
- notificationIconAreaController,
- shadeExpansionStateManager,
- featureFlags,
- statusBarIconController,
- darkIconManagerFactory,
- collapsedStatusBarViewModel,
- collapsedStatusBarViewBinder,
- statusBarHideIconsForBouncerManager,
- keyguardStateController,
- shadeViewController,
- statusBarStateController,
- commandQueue,
- carrierConfigTracker,
- collapsedStatusBarFragmentLogger,
- operatorNameViewControllerFactory,
- secureSettings,
- mainExecutor,
- dumpManager,
- statusBarWindowStateController,
- keyguardUpdateMonitor);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2efdf8a..66f0f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -29,7 +29,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewStub;
import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
@@ -85,6 +84,8 @@
import java.util.Set;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* Contains the collapsed status bar and handles hiding/showing based on disable flags
* and keyguard state. Also manages lifecycle to make sure the views it contains are being
@@ -203,7 +204,7 @@
mTransitionFromLockscreenToDreamStarted = false;
};
- @SuppressLint("ValidFragment")
+ @Inject
public CollapsedStatusBarFragment(
StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
OngoingCallController ongoingCallController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
new file mode 100644
index 0000000..55af0e3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentStartable.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 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.statusbar.phone.fragment
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fragments.FragmentService
+import com.android.systemui.qs.QSFragmentStartable
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import javax.inject.Provider
+
+/**
+ * Provides [FragmentService] with a way to automatically inflate [CollapsedStatusBarFragment],
+ * similar to [QSFragmentStartable].
+ */
+@SysUISingleton
+class CollapsedStatusBarFragmentStartable
+@Inject
+constructor(
+ private val fragmentService: FragmentService,
+ private val collapsedstatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>
+) : CoreStartable {
+ override fun start() {
+ fragmentService.addFragmentInstantiationProvider(
+ CollapsedStatusBarFragment::class.java,
+ collapsedstatusBarFragmentProvider,
+ )
+ }
+}
+
+@Module(subcomponents = [StatusBarFragmentComponent::class])
+interface CollapsedStatusBarFragmentStartableModule {
+ @Binds
+ @IntoMap
+ @ClassKey(CollapsedStatusBarFragmentStartable::class)
+ fun bindsCollapsedStatusBarFragmentStartable(
+ startable: CollapsedStatusBarFragmentStartable
+ ): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 4b2fb43..249ca35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -26,6 +26,7 @@
import android.widget.Space
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
@@ -64,7 +65,7 @@
val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
- view.isVisible = true
+ view.isVisible = viewModel.isVisible.value
iconView.isVisible = true
// TODO(b/238425913): We should log this visibility state.
@@ -77,108 +78,122 @@
var isCollecting = false
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- logger.logCollectionStarted(view, viewModel)
- isCollecting = true
-
- launch {
- visibilityState.collect { state ->
- when (state) {
- STATE_ICON -> {
- mobileGroupView.visibility = VISIBLE
- dotView.visibility = GONE
- }
- STATE_DOT -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = VISIBLE
- }
- STATE_HIDDEN -> {
- mobileGroupView.visibility = INVISIBLE
- dotView.visibility = INVISIBLE
- }
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ // isVisible controls the visibility state of the outer group, and thus it needs
+ // to run in the CREATED lifecycle so it can continue to watch while invisible
+ // See (b/291031862) for details
+ launch {
+ viewModel.isVisible.collect { isVisible ->
+ viewModel.verboseLogger?.logBinderReceivedVisibility(
+ view,
+ viewModel.subscriptionId,
+ isVisible
+ )
+ view.isVisible = isVisible
+ // [StatusIconContainer] can get out of sync sometimes. Make sure to
+ // request another layout when this changes.
+ view.requestLayout()
}
}
}
+ }
- launch {
- viewModel.isVisible.collect { isVisible ->
- viewModel.verboseLogger?.logBinderReceivedVisibility(
- view,
- viewModel.subscriptionId,
- isVisible
- )
- view.isVisible = isVisible
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ logger.logCollectionStarted(view, viewModel)
+ isCollecting = true
+
+ launch {
+ visibilityState.collect { state ->
+ when (state) {
+ STATE_ICON -> {
+ mobileGroupView.visibility = VISIBLE
+ dotView.visibility = GONE
+ }
+ STATE_DOT -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = VISIBLE
+ }
+ STATE_HIDDEN -> {
+ mobileGroupView.visibility = INVISIBLE
+ dotView.visibility = INVISIBLE
+ }
+ }
+ }
}
- }
- // Set the icon for the triangle
- launch {
- viewModel.icon.distinctUntilChanged().collect { icon ->
- viewModel.verboseLogger?.logBinderReceivedSignalIcon(
- view,
- viewModel.subscriptionId,
- icon,
- )
- mobileDrawable.level = icon.toSignalDrawableState()
+ // Set the icon for the triangle
+ launch {
+ viewModel.icon.distinctUntilChanged().collect { icon ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ icon,
+ )
+ mobileDrawable.level = icon.toSignalDrawableState()
+ }
}
- }
- launch {
- viewModel.contentDescription.distinctUntilChanged().collect {
- ContentDescriptionViewBinder.bind(it, view)
+ launch {
+ viewModel.contentDescription.distinctUntilChanged().collect {
+ ContentDescriptionViewBinder.bind(it, view)
+ }
}
- }
- // Set the network type icon
- launch {
- viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
- viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
- view,
- viewModel.subscriptionId,
- dataTypeId,
- )
- dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
- networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ // Set the network type icon
+ launch {
+ viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+ view,
+ viewModel.subscriptionId,
+ dataTypeId,
+ )
+ dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+ networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ }
}
- }
- // Set the roaming indicator
- launch {
- viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
- roamingView.isVisible = isRoaming
- roamingSpace.isVisible = isRoaming
+ // Set the roaming indicator
+ launch {
+ viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+ roamingView.isVisible = isRoaming
+ roamingSpace.isVisible = isRoaming
+ }
}
- }
- // Set the activity indicators
- launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+ // Set the activity indicators
+ launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
- launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+ launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
- launch {
- viewModel.activityContainerVisible.collect { activityContainer.isVisible = it }
- }
-
- // Set the tint
- launch {
- iconTint.collect { tint ->
- val tintList = ColorStateList.valueOf(tint)
- iconView.imageTintList = tintList
- networkTypeView.imageTintList = tintList
- roamingView.imageTintList = tintList
- activityIn.imageTintList = tintList
- activityOut.imageTintList = tintList
- dotView.setDecorColor(tint)
+ launch {
+ viewModel.activityContainerVisible.collect {
+ activityContainer.isVisible = it
+ }
}
- }
- launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+ // Set the tint
+ launch {
+ iconTint.collect { tint ->
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ networkTypeView.imageTintList = tintList
+ roamingView.imageTintList = tintList
+ activityIn.imageTintList = tintList
+ activityOut.imageTintList = tintList
+ dotView.setDecorColor(tint)
+ }
+ }
- try {
- awaitCancellation()
- } finally {
- isCollecting = false
- logger.logCollectionStopped(view, viewModel)
+ launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ logger.logCollectionStopped(view, viewModel)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
index 120ba4e..b6f1677 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt
@@ -65,7 +65,7 @@
// [DefaultConnectionModel]
private val wifiIconFlow: Flow<InternetTileModel> =
wifiInteractor.wifiNetwork.flatMapLatest {
- val wifiIcon = WifiIcon.fromModel(it, context)
+ val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
flowOf(
InternetTileModel.Active(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
index 8156500..668c5b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -65,8 +65,18 @@
@VisibleForTesting
internal val NO_INTERNET = R.string.data_connection_no_internet
- /** Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon] */
- fun fromModel(model: WifiNetworkModel, context: Context): WifiIcon =
+ /**
+ * Mapping from a [WifiNetworkModel] to the appropriate [WifiIcon].
+ *
+ * @param showHotspotInfo true if the wifi icon should represent the hotspot device (if it
+ * exists) and false if the wifi icon should only ever show the wifi level and *not* the
+ * hotspot device.
+ */
+ fun fromModel(
+ model: WifiNetworkModel,
+ context: Context,
+ showHotspotInfo: Boolean,
+ ): WifiIcon =
when (model) {
is WifiNetworkModel.Unavailable -> Hidden
is WifiNetworkModel.Invalid -> Hidden
@@ -82,22 +92,50 @@
)
is WifiNetworkModel.Active -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[model.level])
- when {
- model.isValidated ->
- Visible(
- WifiIcons.WIFI_FULL_ICONS[model.level],
- ContentDescription.Loaded(levelDesc),
- )
- else ->
- Visible(
- WifiIcons.WIFI_NO_INTERNET_ICONS[model.level],
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- ),
- )
- }
+ val contentDescription =
+ ContentDescription.Loaded(
+ if (model.isValidated) {
+ (levelDesc)
+ } else {
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ }
+ )
+ Visible(model.toIcon(showHotspotInfo), contentDescription)
}
}
+
+ @DrawableRes
+ private fun WifiNetworkModel.Active.toIcon(showHotspotInfo: Boolean): Int {
+ return if (!showHotspotInfo) {
+ this.toBasicIcon()
+ } else {
+ when (this.hotspotDeviceType) {
+ WifiNetworkModel.HotspotDeviceType.NONE -> this.toBasicIcon()
+ WifiNetworkModel.HotspotDeviceType.TABLET ->
+ com.android.settingslib.R.drawable.ic_hotspot_tablet
+ WifiNetworkModel.HotspotDeviceType.LAPTOP ->
+ com.android.settingslib.R.drawable.ic_hotspot_laptop
+ WifiNetworkModel.HotspotDeviceType.WATCH ->
+ com.android.settingslib.R.drawable.ic_hotspot_watch
+ WifiNetworkModel.HotspotDeviceType.AUTO ->
+ com.android.settingslib.R.drawable.ic_hotspot_auto
+ // Use phone as the default drawable
+ WifiNetworkModel.HotspotDeviceType.PHONE,
+ WifiNetworkModel.HotspotDeviceType.UNKNOWN,
+ WifiNetworkModel.HotspotDeviceType.INVALID ->
+ com.android.settingslib.R.drawable.ic_hotspot_phone
+ }
+ }
+ }
+
+ @DrawableRes
+ private fun WifiNetworkModel.Active.toBasicIcon(): Int {
+ return if (this.isValidated) {
+ WifiIcons.WIFI_FULL_ICONS[this.level]
+ } else {
+ WifiIcons.WIFI_NO_INTERNET_ICONS[this.level]
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 27ac7b9..d099c8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -66,11 +66,6 @@
@Application private val scope: CoroutineScope,
wifiConstants: WifiConstants,
) : WifiViewModelCommon {
- /** Returns the icon to use based on the given network. */
- private fun WifiNetworkModel.icon(): WifiIcon {
- return WifiIcon.fromModel(this, context)
- }
-
override val wifiIcon: StateFlow<WifiIcon> =
combine(
interactor.isEnabled,
@@ -82,7 +77,8 @@
return@combine WifiIcon.Hidden
}
- val icon = wifiNetwork.icon()
+ // Don't show any hotspot info in the status bar.
+ val icon = WifiIcon.fromModel(wifiNetwork, context, showHotspotInfo = false)
return@combine when {
isDefault -> icon
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 3a94730..e1b608f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -16,15 +16,16 @@
package com.android.keyguard
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.ImageView
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
@@ -45,7 +46,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 1acd676..93048a5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -16,15 +16,16 @@
package com.android.keyguard
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -52,7 +53,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
private lateinit var mKeyguardPatternView: KeyguardPatternView
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index efe1955..2b90e7c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -22,16 +22,17 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -46,7 +47,8 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4.class)
@RunWithLooper
public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 80fd721..61acacd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -16,16 +16,17 @@
package com.android.keyguard
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -52,7 +53,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardPinViewControllerTest : SysuiTestCase() {
@@ -175,7 +177,8 @@
private fun getPinTopGuideline(): Float {
val cs = ConstraintSet()
- val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
+ val container =
+ objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
cs.clone(container)
return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 80172a1..6bff4ce 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -21,7 +21,6 @@
import android.hardware.biometrics.BiometricOverlayConstants
import android.media.AudioManager
import android.telephony.TelephonyManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
import android.view.Gravity
@@ -30,6 +29,7 @@
import android.view.View
import android.view.WindowInsetsController
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
@@ -37,6 +37,7 @@
import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
@@ -96,7 +97,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@RunWithLooper
class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index f6450a4..3e330d65 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -44,7 +44,6 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Insets;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
import android.view.View;
@@ -54,9 +53,11 @@
import android.window.OnBackAnimationCallback;
import androidx.constraintlayout.widget.ConstraintSet;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.plugins.FalsingManager;
@@ -75,7 +76,8 @@
import java.util.ArrayList;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 64e1458..68c2f59 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -25,17 +25,18 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowInsetsController;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
+import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
@@ -49,7 +50,8 @@
import org.mockito.junit.MockitoRule;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 291dda25..4db5f35 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -18,13 +18,14 @@
import android.telephony.PinResult
import android.telephony.TelephonyManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
@@ -43,7 +44,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardSimPinViewControllerTest : SysuiTestCase() {
private lateinit var simPinView: KeyguardSimPinView
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 626faa6..47ff3b9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -18,13 +18,14 @@
import android.telephony.PinResult
import android.telephony.TelephonyManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
@@ -39,7 +40,8 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardSimPukViewControllerTest : SysuiTestCase() {
private lateinit var simPukView: KeyguardSimPukView
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index b9e3f14..09ff546 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -24,7 +24,6 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -148,7 +147,6 @@
mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
mUnderTest = new LockIconViewController(
- mLockIconView,
mStatusBarStateController,
mKeyguardUpdateMonitor,
mKeyguardViewController,
@@ -167,7 +165,8 @@
.getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
- mPrimaryBouncerInteractor
+ mPrimaryBouncerInteractor,
+ mContext
);
}
@@ -228,9 +227,6 @@
protected void init(boolean useMigrationFlag) {
mFeatureFlags.set(DOZING_MIGRATION_1, useMigrationFlag);
- mUnderTest.init();
-
- verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachCaptor.getValue().onViewAttachedToWindow(mLockIconView);
+ mUnderTest.setLockIconView(mLockIconView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 45021ba..979fc83 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -52,6 +52,12 @@
@TestableLooper.RunWithLooper
public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ when(mLockIconView.isAttachedToWindow()).thenReturn(true);
+ }
+
@Test
public void testUpdateFingerprintLocationOnInit() {
// GIVEN fp sensor location is available pre-attached
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index b100336..f9830b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -71,6 +71,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -163,6 +164,204 @@
}
@Test
+ public void startListening_fetchesCurrentActive_none() {
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of());
+
+ mController.setListening(true);
+
+ assertThat(mController.getActiveAppOps()).isEmpty();
+ }
+
+ /** Regression test for b/294104969. */
+ @Test
+ public void startListening_fetchesCurrentActive_oneActive() {
+ AppOpsManager.PackageOps packageOps = createPackageOp(
+ "package.test",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the op
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.test");
+ assertThat(first.getUid()).isEqualTo(2);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multiplePackages() {
+ AppOpsManager.PackageOps packageOps1 = createPackageOp(
+ "package.one",
+ /* packageUid= */ 1,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps2 = createPackageOp(
+ "package.two",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ false);
+ AppOpsManager.PackageOps packageOps3 = createPackageOp(
+ "package.three",
+ /* packageUid= */ 3,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+
+ AppOpItem item0 = list.get(0);
+ assertThat(item0.getPackageName()).isEqualTo("package.one");
+ assertThat(item0.getUid()).isEqualTo(1);
+ assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+
+ AppOpItem item1 = list.get(1);
+ assertThat(item1.getPackageName()).isEqualTo("package.three");
+ assertThat(item1.getUid()).isEqualTo(3);
+ assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multipleEntries() {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getUid()).thenReturn(1);
+ when(packageOps.getPackageName()).thenReturn("package.one");
+
+ // Entry 1
+ AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
+ when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
+ AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed1.isRunning()).thenReturn(true);
+ when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
+ // Entry 2
+ AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
+ when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
+ AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed2.isRunning()).thenReturn(true);
+ when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
+ // Entry 3
+ AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
+ when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
+ AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed3.isRunning()).thenReturn(false);
+ when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
+
+ when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.one");
+ assertThat(first.getUid()).isEqualTo(1);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
+
+ AppOpItem second = list.get(1);
+ assertThat(second.getPackageName()).isEqualTo("package.one");
+ assertThat(second.getUid()).isEqualTo(1);
+ assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
+ }
+
+ @Test
+ public void startListening_fetchesCurrentActive_multipleAttributes() {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getUid()).thenReturn(1);
+ when(packageOps.getPackageName()).thenReturn("package.one");
+ AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+ when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
+
+ AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed1.isRunning()).thenReturn(false);
+ AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed2.isRunning()).thenReturn(true);
+ AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed3.isRunning()).thenReturn(true);
+ when(entry.getAttributedOpEntries()).thenReturn(
+ Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
+
+ when(packageOps.getOps()).thenReturn(List.of(entry));
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps));
+
+ // WHEN we start listening
+ mController.setListening(true);
+
+ // THEN the active list has the ops
+ List<AppOpItem> list = mController.getActiveAppOps();
+ // Multiple attributes get merged into one entry in the active ops
+ assertEquals(1, list.size());
+
+ AppOpItem first = list.get(0);
+ assertThat(first.getPackageName()).isEqualTo("package.one");
+ assertThat(first.getUid()).isEqualTo(1);
+ assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
+ }
+
+ /** Regression test for b/294104969. */
+ @Test
+ public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
+ AppOpsManager.PackageOps packageOps1 = createPackageOp(
+ "package.one",
+ /* packageUid= */ 1,
+ AppOpsManager.OPSTR_FINE_LOCATION,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps2 = createPackageOp(
+ "package.two",
+ /* packageUid= */ 2,
+ AppOpsManager.OPSTR_RECORD_AUDIO,
+ /* isRunning= */ true);
+ AppOpsManager.PackageOps packageOps3 = createPackageOp(
+ "package.three",
+ /* packageUid= */ 3,
+ AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
+ /* isRunning= */ true);
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+ .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+ // WHEN we start listening
+ mController.addCallback(
+ new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
+ mCallback);
+ mTestableLooper.processAllMessages();
+
+ // THEN the callback is notified of the current active ops it cares about
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION,
+ /* uid= */ 1,
+ "package.one",
+ true);
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_RECORD_AUDIO,
+ /* uid= */ 2,
+ "package.two",
+ true);
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+ /* uid= */ 3,
+ "package.three",
+ true);
+ }
+
+ @Test
public void addCallback_includedCode() {
mController.addCallback(
new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
@@ -772,6 +971,22 @@
assertFalse(list.get(1).isDisabled());
}
+ private AppOpsManager.PackageOps createPackageOp(
+ String packageName, int packageUid, String opStr, boolean isRunning) {
+ AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+ when(packageOps.getPackageName()).thenReturn(packageName);
+ when(packageOps.getUid()).thenReturn(packageUid);
+ AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+ when(entry.getOpStr()).thenReturn(opStr);
+ AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
+ when(attributed.isRunning()).thenReturn(isRunning);
+
+ when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
+ when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
+
+ return packageOps;
+ }
+
private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) {
mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 88c710a..ddb482f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,18 +16,46 @@
package com.android.systemui.back.domain.interactor
+import android.view.ViewRootImpl
+import android.window.BackEvent
+import android.window.BackEvent.EDGE_LEFT
+import android.window.OnBackAnimationCallback
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import android.window.WindowOnBackInvokedDispatcher
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -41,7 +69,12 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
+ private val testScope = TestScope()
+ private val featureFlags = FakeFeatureFlags()
+ private val executor = FakeExecutor(FakeSystemClock())
+
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -49,18 +82,42 @@
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var qsController: QuickSettingsController
@Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var windowRootView: WindowRootView
+ @Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
+ @Mock private lateinit var iStatusBarService: IStatusBarService
+ @Mock private lateinit var headsUpManager: HeadsUpManager
- private lateinit var backActionInteractor: BackActionInteractor
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ WindowRootViewVisibilityRepository(iStatusBarService, executor),
+ keyguardRepository,
+ headsUpManager,
+ )
+ }
- @Before
- fun setup() {
- backActionInteractor =
- BackActionInteractor(
+ private val backActionInteractor: BackActionInteractor by lazy {
+ BackActionInteractor(
+ testScope.backgroundScope,
statusBarStateController,
statusBarKeyguardViewManager,
shadeController,
+ notificationShadeWindowController,
+ windowRootViewVisibilityInteractor,
+ featureFlags,
)
- backActionInteractor.setup(qsController, shadeViewController)
+ .apply { this.setup(qsController, shadeViewController) }
+ }
+
+ @Before
+ fun setUp() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+ whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
+ whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
+ whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
}
@Test
@@ -117,4 +174,139 @@
verify(statusBarKeyguardViewManager, never()).onBackPressed()
verify(shadeViewController, never()).animateCollapseQs(anyBoolean())
}
+
+ @Test
+ fun shadeVisibleAndDeviceAwake_callbackRegistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher)
+ .registerOnBackInvokedCallback(eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT), any())
+ }
+
+ @Test
+ fun noWindowRootView_noCrashAttemptingCallbackRegistration() {
+ whenever(notificationShadeWindowController.windowRootView).thenReturn(null)
+
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ testScope.runCurrent()
+ // No assert necessary, just testing no crash
+ }
+
+ @Test
+ fun shadeNotVisible_callbackUnregistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false)
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+ }
+
+ @Test
+ fun deviceAsleep_callbackUnregistered() {
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+
+ setWakefulness(WakefulnessState.ASLEEP)
+ testScope.runCurrent()
+
+ verify(onBackInvokedDispatcher).unregisterOnBackInvokedCallback(callback)
+ }
+
+ @Test
+ fun animationFlagOff_onBackInvoked_keyguardNotified() {
+ backActionInteractor.start()
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+ whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+ callback.onBackInvoked()
+
+ verify(statusBarKeyguardViewManager).onBackPressed()
+ }
+
+ @Test
+ fun animationFlagOn_onBackInvoked_keyguardNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback()
+ whenever(statusBarKeyguardViewManager.canHandleBackPressed()).thenReturn(true)
+
+ callback.onBackInvoked()
+
+ verify(statusBarKeyguardViewManager).onBackPressed()
+ }
+
+ @Test
+ fun animationFlagOn_callbackIsAnimationCallback() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ val callback = getBackInvokedCallback()
+
+ assertThat(callback).isInstanceOf(OnBackAnimationCallback::class.java)
+ }
+
+ @Test
+ fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+ whenever(shadeViewController.canBeCollapsed()).thenReturn(false)
+
+ callback.onBackProgressed(createBackEvent(0.3f))
+
+ verify(shadeViewController, never()).onBackProgressed(0.3f)
+ }
+
+ @Test
+ fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
+ featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
+ backActionInteractor.start()
+ windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ val callback = getBackInvokedCallback() as OnBackAnimationCallback
+
+ whenever(shadeViewController.canBeCollapsed()).thenReturn(true)
+
+ callback.onBackProgressed(createBackEvent(0.4f))
+
+ verify(shadeViewController).onBackProgressed(0.4f)
+ }
+
+ private fun getBackInvokedCallback(): OnBackInvokedCallback {
+ testScope.runCurrent()
+ val captor = argumentCaptor<OnBackInvokedCallback>()
+ verify(onBackInvokedDispatcher).registerOnBackInvokedCallback(any(), captor.capture())
+ return captor.value!!
+ }
+
+ private fun createBackEvent(progress: Float): BackEvent =
+ BackEvent(/* touchX= */ 0f, /* touchY= */ 0f, progress, /* swipeEdge= */ EDGE_LEFT)
+
+ private fun setWakefulness(state: WakefulnessState) {
+ val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ keyguardRepository.setWakefulnessModel(model)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 47084c0..0ed46da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -20,6 +20,7 @@
import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
@@ -500,6 +501,81 @@
}
@Test
+ fun auto_confirm_authentication_when_finger_down() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticating).isFalse()
+ assertThat(canTryAgain).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+
+ if (testCase.isFaceOnly && expectConfirmation) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
+ }
+
+ @Test
+ fun cannot_auto_confirm_authentication_when_finger_up() = runGenericTest {
+ val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+
+ // No icon button when face only, can't confirm before auth
+ if (!testCase.isFaceOnly) {
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_DOWN))
+ viewModel.onOverlayTouch(obtainMotionEvent(MotionEvent.ACTION_UP))
+ }
+ viewModel.showAuthenticated(testCase.authenticatedModality, 0)
+
+ val authenticating by collectLastValue(viewModel.isAuthenticating)
+ val authenticated by collectLastValue(viewModel.isAuthenticated)
+ val message by collectLastValue(viewModel.message)
+ val size by collectLastValue(viewModel.size)
+ val legacyState by collectLastValue(viewModel.legacyState)
+ val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
+
+ assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
+ if (expectConfirmation) {
+ assertThat(size).isEqualTo(PromptSize.MEDIUM)
+ assertButtonsVisible(
+ cancel = true,
+ confirm = true,
+ )
+
+ viewModel.confirmAuthenticated()
+ assertThat(message).isEqualTo(PromptMessage.Empty)
+ assertButtonsVisible()
+ }
+
+ assertThat(authenticating).isFalse()
+ assertThat(authenticated?.isAuthenticated).isTrue()
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ assertThat(canTryAgain).isFalse()
+ }
+
+ @Test
fun cannot_confirm_unless_authenticated() = runGenericTest {
val authenticating by collectLastValue(viewModel.isAuthenticating)
val authenticated by collectLastValue(viewModel.isAuthenticated)
@@ -679,6 +755,10 @@
testScope.runTest { block() }
}
+ /** Obtain a MotionEvent with the specified MotionEvent action constant */
+ private fun obtainMotionEvent(action: Int): MotionEvent =
+ MotionEvent.obtain(0, 0, action, 0f, 0f, 0)
+
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index 937a7a9..037c1ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.eq
@@ -55,6 +56,8 @@
@Mock
lateinit var centralSurfaces: CentralSurfaces
@Mock
+ lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock
lateinit var keyguardStateController: KeyguardStateController
@Mock
lateinit var packageManager: PackageManager
@@ -91,6 +94,7 @@
context = mock(),
centralSurfaces = centralSurfaces,
keyguardStateController = keyguardStateController,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
packageManager = packageManager,
activityManager = activityManager,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
index e3a75f1..4ad9549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
@@ -2,6 +2,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -28,9 +29,16 @@
}
@Test
- fun apply() {
+ fun addView() {
+ val constraintLayout = ConstraintLayout(context, null)
+ blueprint.addViews(constraintLayout)
+ verify(widgetSection).addViews(constraintLayout)
+ }
+
+ @Test
+ fun applyConstraints() {
val cs = ConstraintSet()
- blueprint.apply(cs)
- verify(widgetSection).apply(cs)
+ blueprint.applyConstraints(cs)
+ verify(widgetSection).applyConstraints(cs)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 0b27bc9..54f66dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
-import dagger.Lazy
import java.util.Optional
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -173,10 +172,9 @@
private fun setupComponent(enabled: Boolean): ControlsComponent {
return ControlsComponent(
enabled,
- mContext,
- Lazy { controller },
- Lazy { uiController },
- Lazy { listingController },
+ { controller },
+ { uiController },
+ { listingController },
lockPatternUtils,
keyguardStateController,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index b1061ba..74d0d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.concurrency.FakeExecutor
@@ -123,9 +122,6 @@
arrayOf(componentName.packageName)
)
- // Return false by default, we'll test the true path
- `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
-
val wrapper = object : ContextWrapper(mContext) {
override fun createContextAsUser(user: UserHandle, flags: Int): Context {
return baseContext
@@ -469,38 +465,7 @@
}
@Test
- fun testPackageNotPreferred_nullPanel() {
- mContext.orCreateTestableResources
- .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
-
- val serviceInfo = ServiceInfo(
- componentName,
- activityName
- )
-
- `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
- .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
-
- setUpQueryResult(listOf(
- ActivityInfo(
- activityName,
- exported = true,
- permission = Manifest.permission.BIND_CONTROLS
- )
- ))
-
- val list = listOf(serviceInfo)
- serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
- executor.runAllReady()
-
- assertNull(controller.getCurrentServices()[0].panelActivity)
- }
-
- @Test
- fun testPackageNotPreferred_allowAllApps_correctPanel() {
- `when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(true)
-
+ fun testPackageNotPreferred_correctPanel() {
mContext.orCreateTestableResources
.addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 9be54fb..db7c003 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -165,13 +165,78 @@
assertThat(value?.ids()).containsExactly(1, 2, 3, 4)
}
+ @Test
+ fun onDisplayConnected_pendingDisplayReceived() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun onDisplayDisconnected_pendingDisplayNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(1)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
+ @Test
+ fun onDisplayDisconnected_unknownDisplay_doesNotSendNull() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+
+ assertThat(pendingDisplay).isNotNull()
+
+ displayListener.value.onDisplayDisconnected(2)
+
+ assertThat(pendingDisplay).isNotNull()
+ }
+
+ @Test
+ fun onDisplayConnected_multipleTimes_sendsOnlyTheLastOne() =
+ testScope.runTest {
+ val pendingDisplay by latestPendingDisplayFlowValue()
+ displayListener.value.onDisplayConnected(1)
+ displayListener.value.onDisplayConnected(2)
+
+ assertThat(pendingDisplay).isEqualTo(2)
+ }
+
private fun Iterable<Display>.ids(): List<Int> = map { it.displayId }
// Wrapper to capture the displayListener.
private fun TestScope.latestDisplayFlowValue(): FlowValue<Set<Display>?> {
val flowValue = collectLastValue(displayRepository.displays)
verify(displayManager)
- .registerDisplayListener(displayListener.capture(), eq(testHandler), anyLong())
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(
+ DisplayManager.EVENT_FLAG_DISPLAY_ADDED or
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED or
+ DisplayManager.EVENT_FLAG_DISPLAY_REMOVED
+ )
+ )
+ return flowValue
+ }
+
+ private fun TestScope.latestPendingDisplayFlowValue(): FlowValue<Int?> {
+ val flowValue = collectLastValue(displayRepository.pendingDisplayId)
+ verify(displayManager)
+ .registerDisplayListener(
+ displayListener.capture(),
+ eq(testHandler),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+ )
return flowValue
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index eb0ad69..50617a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.display.domain.interactor
+import android.hardware.display.DisplayManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
@@ -27,14 +28,20 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -42,11 +49,22 @@
@SmallTest
class ConnectedDisplayInteractorTest : SysuiTestCase() {
+ private val displayManager = mock<DisplayManager>()
private val fakeDisplayRepository = FakeDisplayRepository()
+ private val fakeKeyguardRepository = FakeKeyguardRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
- ConnectedDisplayInteractorImpl(fakeDisplayRepository)
+ ConnectedDisplayInteractorImpl(
+ displayManager,
+ fakeKeyguardRepository,
+ fakeDisplayRepository
+ )
private val testScope = TestScope(UnconfinedTestDispatcher())
+ @Before
+ fun setup() {
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+ }
+
@Test
fun displayState_nullDisplays_disconnected() =
testScope.runTest {
@@ -126,6 +144,70 @@
assertThat(value).isEqualTo(State.CONNECTED_SECURE)
}
+ @Test
+ fun pendingDisplay_propagated() =
+ testScope.runTest {
+ val value by lastPendingDisplay()
+ val pendingDisplayId = 4
+
+ fakeDisplayRepository.emit(pendingDisplayId)
+
+ assertThat(value).isNotNull()
+ }
+
+ @Test
+ fun onPendingDisplay_enable_displayEnabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.enable()
+
+ Mockito.verify(displayManager).enableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_disable_displayDisabled() =
+ testScope.runTest {
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ pendingDisplay!!.disable()
+
+ Mockito.verify(displayManager).disableConnectedDisplay(eq(1))
+ }
+
+ @Test
+ fun onPendingDisplay_keyguardUnlocked_returnsPendingDisplay() =
+ testScope.runTest {
+ fakeKeyguardRepository.setKeyguardUnlocked(false)
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ assertThat(pendingDisplay).isNull()
+
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+
+ assertThat(pendingDisplay).isNotNull()
+ }
+
+ @Test
+ fun onPendingDisplay_keyguardLocked_returnsNull() =
+ testScope.runTest {
+ fakeKeyguardRepository.setKeyguardUnlocked(true)
+ val pendingDisplay by lastPendingDisplay()
+
+ fakeDisplayRepository.emit(1)
+ assertThat(pendingDisplay).isNotNull()
+
+ fakeKeyguardRepository.setKeyguardUnlocked(false)
+
+ assertThat(pendingDisplay).isNull()
+ }
+
private fun TestScope.lastValue(): FlowValue<State?> =
collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
+
+ private fun TestScope.lastPendingDisplay(): FlowValue<PendingDisplay?> =
+ collectLastValue(connectedDisplayStateProvider.pendingDisplay)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
new file mode 100644
index 0000000..7059647
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 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.display.ui.view
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogTest : SysuiTestCase() {
+
+ private lateinit var dialog: MirroringConfirmationDialog
+
+ private val onStartMirroringCallback = mock<View.OnClickListener>()
+ private val onCancelCallback = mock<View.OnClickListener>()
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ dialog = MirroringConfirmationDialog(context, onStartMirroringCallback, onCancelCallback)
+ }
+
+ @Test
+ fun startMirroringButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ verify(onStartMirroringCallback).onClick(any())
+ verify(onCancelCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun cancelButton_clicked_callsCorrectCallback() {
+ dialog.show()
+
+ dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+ verify(onCancelCallback).onClick(any())
+ verify(onStartMirroringCallback, never()).onClick(any())
+ }
+
+ @After
+ fun teardown() {
+ if (::dialog.isInitialized) {
+ dialog.dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index ccd631e..8f66344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -9,6 +9,7 @@
import android.graphics.drawable.Icon
import android.graphics.drawable.VectorDrawable
import android.net.Uri
+import android.util.Size
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -78,12 +79,19 @@
}
@Test
- fun invalidIcon_returnsNull() =
+ fun invalidIcon_loadDrawable_returnsNull() =
testScope.runTest {
assertThat(imageLoader.loadDrawable(Icon.createWithFilePath("this is broken"))).isNull()
}
@Test
+ fun invalidIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(imageLoader.loadSize(Icon.createWithFilePath("this is broken"), context))
+ .isNull()
+ }
+
+ @Test
fun invalidIS_returnsNull() =
testScope.runTest {
assertThat(
@@ -172,6 +180,17 @@
}
@Test
+ fun validBitmapIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ assertThat(imageLoader.loadSize(Icon.createWithBitmap(bitmap), context)).isNull()
+ }
+
+ @Test
fun validUriIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -186,6 +205,17 @@
}
@Test
+ fun validUriIcon_returnsSize() =
+ testScope.runTest {
+ val drawable = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+ val uri =
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ val loadedSize =
+ imageLoader.loadSize(Icon.createWithContentUri(Uri.parse(uri)), context)
+ assertSizeEqualToDrawableSize(loadedSize, drawable)
+ }
+
+ @Test
fun validDataIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -205,6 +235,54 @@
}
@Test
+ fun validDataIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ val bitmap =
+ BitmapFactory.decodeResource(
+ context.resources,
+ R.drawable.dessert_zombiegingerbread
+ )
+ val bos =
+ ByteArrayOutputStream(
+ bitmap.byteCount * 2
+ ) // Compressed bitmap should be smaller than its source.
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
+
+ val array = bos.toByteArray()
+ assertThat(imageLoader.loadSize(Icon.createWithData(array, 0, array.size), context))
+ .isNull()
+ }
+
+ @Test
+ fun validResourceIcon_returnsBitmapDrawable() =
+ testScope.runTest {
+ val bitmap = context.resources.getDrawable(R.drawable.dessert_zombiegingerbread)
+ val loadedDrawable =
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "com.android.systemui.tests",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ assertBitmapEqualToDrawable(loadedDrawable, (bitmap as BitmapDrawable).bitmap)
+ }
+
+ @Test
+ fun validResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadSize(
+ Icon.createWithResource(
+ "com.android.systemui.tests",
+ R.drawable.dessert_zombiegingerbread
+ ),
+ context
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun validSystemResourceIcon_returnsBitmapDrawable() =
testScope.runTest {
val bitmap =
@@ -217,6 +295,18 @@
}
@Test
+ fun validSystemResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadSize(
+ Icon.createWithResource("android", android.R.drawable.ic_dialog_alert),
+ context
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun invalidDifferentPackageResourceIcon_returnsNull() =
testScope.runTest {
val loadedDrawable =
@@ -230,6 +320,20 @@
}
@Test
+ fun invalidDifferentPackageResourceIcon_loadSize_returnsNull() =
+ testScope.runTest {
+ assertThat(
+ imageLoader.loadDrawable(
+ Icon.createWithResource(
+ "noooope.wrong.package",
+ R.drawable.dessert_zombiegingerbread
+ )
+ )
+ )
+ .isNull()
+ }
+
+ @Test
fun validBitmapResource_widthMoreRestricted_downsizesKeepingAspectRatio() =
testScope.runTest {
val loadedDrawable =
@@ -343,4 +447,10 @@
assertThat(actual?.width).isEqualTo(expected.width)
assertThat(actual?.height).isEqualTo(expected.height)
}
+
+ private fun assertSizeEqualToDrawableSize(actual: Size?, expected: Drawable) {
+ assertThat(actual).isNotNull()
+ assertThat(actual?.width).isEqualTo(expected.intrinsicWidth)
+ assertThat(actual?.height).isEqualTo(expected.intrinsicHeight)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
new file mode 100644
index 0000000..71a56cd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.haptics.slider
+
+import android.widget.SeekBar
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SeekableSliderEventProducerTest : SysuiTestCase() {
+
+ private val seekBar = SeekBar(mContext)
+ private val eventProducer = SeekableSliderEventProducer()
+ private val eventFlow = eventProducer.produceEvents()
+
+ @Test
+ fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStartTrackingTouch(seekBar)
+
+ assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest)
+ }
+
+ @Test
+ fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest {
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onStopTrackingTouch(seekBar)
+
+ assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest)
+ }
+
+ @Test
+ fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, true)
+
+ assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest)
+ }
+
+ @Test
+ fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() =
+ runTest {
+ // No-width slider where the min and max values are the same
+ seekBar.min = 100
+ seekBar.max = 100
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, true)
+
+ assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+
+ assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest)
+ }
+
+ @Test
+ fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() =
+ runTest {
+ // No-width slider where the min and max values are the same
+ seekBar.min = 100
+ seekBar.max = 100
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, false)
+
+ assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest)
+ }
+
+ @Test
+ fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() =
+ runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, true)
+ eventProducer.onStartTrackingTouch(seekBar)
+
+ assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest)
+ }
+
+ @Test
+ fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() =
+ runTest {
+ val progress = 50
+ val latest by collectLastValue(eventFlow)
+
+ eventProducer.onProgressChanged(seekBar, progress, true)
+ eventProducer.onStopTrackingTouch(seekBar)
+
+ assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index daafba2..f78d051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -221,6 +221,7 @@
mSystemClock = new FakeSystemClock();
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
+ when(mPowerManager.isInteractive()).thenReturn(true);
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
@@ -241,6 +242,7 @@
mConfigurationController,
mViewMediator,
mKeyguardBypassController,
+ mUiBgExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
@@ -868,8 +870,6 @@
when(mKeyguardStateController.isShowing()).thenReturn(true);
TestableLooper.get(this).processAllMessages();
- when(mPowerManager.isInteractive()).thenReturn(true);
-
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5ead16b..2691860 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -46,6 +46,7 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -322,20 +323,20 @@
val captor = argumentCaptor<StatusBarStateController.StateListener>()
runCurrent()
- verify(statusBarStateController).addCallback(captor.capture())
+ verify(statusBarStateController, atLeastOnce()).addCallback(captor.capture())
- captor.value.onDozeAmountChanged(0.433f, 0.4f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.433f, 0.4f) }
runCurrent()
- captor.value.onDozeAmountChanged(0.498f, 0.5f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.498f, 0.5f) }
runCurrent()
- captor.value.onDozeAmountChanged(0.661f, 0.65f)
+ captor.allValues.forEach { it.onDozeAmountChanged(0.661f, 0.65f) }
runCurrent()
assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
runCurrent()
- verify(statusBarStateController).removeCallback(captor.value)
+ verify(statusBarStateController).removeCallback(any())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index ca93246..d457605 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1049,7 +1049,6 @@
@Test
fun occludedToAlternateBouncer() =
testScope.runTest {
-
// GIVEN a prior transition has run to OCCLUDED
runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
keyguardRepository.setKeyguardOccluded(true)
@@ -1073,6 +1072,31 @@
}
@Test
+ fun occludedToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to OCCLUDED
+ runTransition(KeyguardState.LOCKSCREEN, KeyguardState.OCCLUDED)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // WHEN primary bouncer shows
+ bouncerRepository.setPrimaryShow(true) // beverlyt
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to AlternateBouncer should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun primaryBouncerToOccluded() =
testScope.runTest {
// GIVEN device not sleeping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index addb181..3b4eab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -19,13 +19,17 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
@@ -34,6 +38,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -50,7 +55,9 @@
private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
@Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
@Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
+ @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection
@Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
+ private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
@@ -64,20 +71,32 @@
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
defaultStatusViewSection,
+ defaultNSSLSection,
splitShadeGuidelines,
+ featureFlags,
)
+ featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, false)
}
@Test
- fun apply() {
+ fun addViews() {
+ val constraintLayout = ConstraintLayout(context, null)
+ underTest.addViews(constraintLayout)
+ underTest.sections.forEach { verify(it, never()).addViews(constraintLayout) }
+ }
+
+ @Test
+ fun addViews_lazyInflateFlagOn() {
+ featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, true)
+ val constraintLayout = ConstraintLayout(context, null)
+ underTest.addViews(constraintLayout)
+ underTest.sections.forEach { verify(it).addViews(constraintLayout) }
+ }
+
+ @Test
+ fun applyConstraints() {
val cs = ConstraintSet()
- underTest.apply(cs)
- verify(defaultIndicationAreaSection).apply(cs)
- verify(defaultLockIconSection).apply(cs)
- verify(defaultShortcutsSection).apply(cs)
- verify(defaultAmbientIndicationAreaSection).apply(cs)
- verify(defaultSettingsPopupMenuSection).apply(cs)
- verify(defaultStatusViewSection).apply(cs)
- verify(splitShadeGuidelines).apply(cs)
+ underTest.applyConstraints(cs)
+ underTest.sections.forEach { verify(it).applyConstraints(cs) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 3dcc03d..798b23e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -22,20 +22,45 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.statusbar.KeyguardIndicationController
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
- private val underTest = DefaultIndicationAreaSection(context)
+ @Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
+ @Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel
+ @Mock private lateinit var indicationController: KeyguardIndicationController
+ @Mock private lateinit var featureFlags: FeatureFlags
+
+ private lateinit var underTest: DefaultIndicationAreaSection
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ DefaultIndicationAreaSection(
+ context,
+ keyguardIndicationAreaViewModel,
+ keyguardRootViewModel,
+ indicationController,
+ featureFlags,
+ )
+ }
@Test
fun apply() {
val cs = ConstraintSet()
- underTest.apply(cs)
+ underTest.applyConstraints(cs)
val constraint = cs.getConstraint(R.id.keyguard_indication_area)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
index 379c03c..1192a80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
@@ -22,10 +22,12 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.LockIconViewController
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
-import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shade.NotificationPanelView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -41,19 +43,30 @@
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
+ @Mock private lateinit var notificationPanelView: NotificationPanelView
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var lockIconViewController: LockIconViewController
private lateinit var underTest: DefaultLockIconSection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
underTest =
- DefaultLockIconSection(keyguardUpdateMonitor, authController, windowManager, context)
+ DefaultLockIconSection(
+ keyguardUpdateMonitor,
+ authController,
+ windowManager,
+ context,
+ notificationPanelView,
+ featureFlags,
+ lockIconViewController
+ )
}
@Test
fun apply() {
val cs = ConstraintSet()
- underTest.apply(cs)
+ underTest.applyConstraints(cs)
val constraint = cs.getConstraint(R.id.lock_icon_view)
@@ -64,7 +77,7 @@
@Test
fun testCenterLockIcon() {
val cs = ConstraintSet()
- underTest.centerLockIcon(Point(5, 6), 1F, 5, cs)
+ underTest.centerLockIcon(Point(5, 6), 1F, cs)
val constraint = cs.getConstraint(R.id.lock_icon_view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index a9f288d..b30dc9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -23,15 +23,13 @@
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
@@ -47,10 +45,9 @@
private val underTest =
LockscreenSceneViewModel(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
+ longPress =
+ KeyguardLongPressViewModel(
+ interactor = mock(),
),
)
@@ -76,30 +73,4 @@
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
}
-
- @Test
- fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(false)
- runCurrent()
-
- underTest.onLockButtonClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- }
-
- @Test
- fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setUnlocked(true)
- runCurrent()
-
- underTest.onLockButtonClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index ef51e47..3961a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.keyguard.TestScopeProvider
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
@@ -105,7 +104,6 @@
@Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@Mock lateinit var configurationController: ConfigurationController
- @Mock lateinit var falsingCollector: FalsingCollector
@Mock lateinit var falsingManager: FalsingManager
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var logger: MediaUiEventLogger
@@ -146,7 +144,6 @@
executor,
mediaDataManager,
configurationController,
- falsingCollector,
falsingManager,
dumpManager,
logger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 338182a..b9ee19b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -36,6 +36,8 @@
import android.os.PowerManager;
import android.os.Temperature;
import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -50,20 +52,17 @@
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Duration;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -93,15 +92,12 @@
private IThermalEventListener mSkinThermalEventListener;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private CommandQueue mCommandQueue;
- @Mock private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
- @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private IVrManager mVrManager;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mCentralSurfacesOptionalLazy.get()).thenReturn(Optional.of(mCentralSurfaces));
-
createPowerUi();
mSkinThermalEventListener = mPowerUI.new SkinThermalEventListener();
mUsbThermalEventListener = mPowerUI.new UsbThermalEventListener();
@@ -143,6 +139,23 @@
}
@Test
+ public void testSkinWarning_throttlingEmergency_butVrMode() throws Exception {
+ mPowerUI.start();
+
+ ArgumentCaptor<IVrStateCallbacks> vrCallback =
+ ArgumentCaptor.forClass(IVrStateCallbacks.class);
+ verify(mVrManager).registerListener(vrCallback.capture());
+
+ vrCallback.getValue().onVrStateChanged(true);
+ final Temperature temp = getEmergencyStatusTemp(Temperature.TYPE_SKIN, "skin2");
+ mSkinThermalEventListener.notifyThrottling(temp);
+
+ TestableLooper.get(this).processAllMessages();
+ // don't show skin high temperature warning when in VR mode
+ verify(mMockWarnings, never()).showHighTemperatureWarning();
+ }
+
+ @Test
public void testUsbAlarm_throttlingCritical() throws Exception {
mPowerUI.start();
@@ -683,8 +696,14 @@
private void createPowerUi() {
mPowerUI = new PowerUI(
- mContext, mBroadcastDispatcher, mCommandQueue, mCentralSurfacesOptionalLazy,
- mMockWarnings, mEnhancedEstimates, mWakefulnessLifecycle, mPowerManager,
+ mContext,
+ mBroadcastDispatcher,
+ mCommandQueue,
+ mVrManager,
+ mMockWarnings,
+ mEnhancedEstimates,
+ mWakefulnessLifecycle,
+ mPowerManager,
mUserTracker);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 0ecbcde..6006cd4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.model.SysUiState
import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
@@ -99,7 +100,8 @@
)
private val sceneContainerViewModel =
SceneContainerViewModel(
- interactor = sceneInteractor,
+ sceneInteractor = sceneInteractor,
+ falsingInteractor = utils.falsingInteractor(),
)
.apply { setTransitionState(transitionState) }
@@ -117,7 +119,10 @@
private val lockscreenSceneViewModel =
LockscreenSceneViewModel(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor = bouncerInteractor,
+ longPress =
+ KeyguardLongPressViewModel(
+ interactor = mock(),
+ ),
)
private val shadeSceneViewModel =
@@ -151,6 +156,7 @@
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
+ falsingCollector = utils.falsingCollector(),
)
startable.start()
@@ -165,9 +171,7 @@
@Test
fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
testScope.runTest {
- lockscreenSceneViewModel.onLockButtonClicked()
- assertCurrentScene(SceneKey.Bouncer)
- emulateUiSceneTransition()
+ emulateUserDrivenTransition(SceneKey.Bouncer)
enterPin()
assertCurrentScene(SceneKey.Gone)
@@ -460,10 +464,7 @@
.that(authenticationInteractor.isUnlocked.value)
.isFalse()
- lockscreenSceneViewModel.onLockButtonClicked()
- runCurrent()
- emulateUiSceneTransition()
-
+ emulateUserDrivenTransition(SceneKey.Bouncer)
enterPin()
emulateUiSceneTransition(
expectedVisible = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
new file mode 100644
index 0000000..2187f36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/WindowRootViewVisibilityRepositoryTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.scene.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class WindowRootViewVisibilityRepositoryTest : SysuiTestCase() {
+ private val iStatusBarService = mock<IStatusBarService>()
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val underTest = WindowRootViewVisibilityRepository(iStatusBarService, executor)
+
+ @Test
+ fun isLockscreenOrShadeVisible_true() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_false() {
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun onLockscreenOrShadeInteractive_statusBarServiceNotified() {
+ underTest.onLockscreenOrShadeInteractive(
+ shouldClearNotificationEffects = true,
+ notificationCount = 3,
+ )
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelRevealed(true, 3)
+ }
+
+ @Test
+ fun onLockscreenOrShadeNotInteractive_statusBarServiceNotified() {
+ underTest.onLockscreenOrShadeNotInteractive()
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelHidden()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
new file mode 100644
index 0000000..f304435
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2023 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.scene.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val iStatusBarService = mock<IStatusBarService>()
+ private val executor = FakeExecutor(FakeSystemClock())
+ private val windowRootViewVisibilityRepository =
+ WindowRootViewVisibilityRepository(iStatusBarService, executor)
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val headsUpManager = mock<HeadsUpManager>()
+ private val notificationPresenter = mock<NotificationPresenter>()
+ private val notificationsController = mock<NotificationsController>()
+
+ private val underTest =
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ windowRootViewVisibilityRepository,
+ keyguardRepository,
+ headsUpManager,
+ )
+ .apply { setUp(notificationPresenter, notificationsController) }
+
+ @Test
+ fun isLockscreenOrShadeVisible_true() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_false() {
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisible_matchesRepo() {
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(true)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isTrue()
+
+ windowRootViewVisibilityRepository.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(underTest.isLockscreenOrShadeVisible.value).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_notVisible_false() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ underTest.setIsLockscreenOrShadeVisible(false)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_deviceAsleep_false() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+ underTest.setIsLockscreenOrShadeVisible(true)
+
+ setWakefulness(WakefulnessState.ASLEEP)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndAwake_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToWake_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.STARTING_TO_WAKE)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLockscreenOrShadeVisibleAndInteractive_visibleAndStartingToSleep_true() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLockscreenOrShadeVisibleAndInteractive)
+
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_statusBarServiceNotified() =
+ testScope.runTest {
+ underTest.start()
+
+ makeLockscreenShadeVisible()
+ testScope.runCurrent()
+ executor.runAllReady()
+
+ verify(iStatusBarService).onPanelRevealed(any(), any())
+ }
+
+ @Test
+ fun lockscreenShadeNotInteractive_statusBarServiceNotified() =
+ testScope.runTest {
+ underTest.start()
+
+ // First, make the shade visible
+ makeLockscreenShadeVisible()
+ testScope.runCurrent()
+ reset(iStatusBarService)
+
+ // WHEN lockscreen or shade is no longer visible
+ underTest.setIsLockscreenOrShadeVisible(false)
+ testScope.runCurrent()
+ executor.runAllReady()
+
+ // THEN status bar service is notified
+ verify(iStatusBarService).onPanelHidden()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_presenterCollapsed_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_nullPresenter_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ underTest.setUp(presenter = null, notificationsController)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateKeyguard_notifEffectsNotCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isFalse()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateShade_presenterNotCollapsed_notifEffectsCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_stateShadeLocked_presenterNotCollapsed_notifEffectsCleared() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+
+ makeLockscreenShadeVisible()
+
+ val shouldClearNotifEffects = argumentCaptor<Boolean>()
+ verify(iStatusBarService).onPanelRevealed(shouldClearNotifEffects.capture(), any())
+ assertThat(shouldClearNotifEffects.value).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(4)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ underTest.setUp(presenter = null, notificationsController)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(9)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(9)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+ whenever(notificationsController.getActiveNotificationsCount()).thenReturn(8)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(8)
+ }
+
+ @Test
+ fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ underTest.setUp(notificationPresenter, notificationsController = null)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(0)
+ }
+
+ private fun makeLockscreenShadeVisible() {
+ underTest.setIsLockscreenOrShadeVisible(true)
+ setWakefulness(WakefulnessState.AWAKE)
+ testScope.runCurrent()
+ executor.runAllReady()
+ }
+
+ private fun setWakefulness(state: WakefulnessState) {
+ val model = WakefulnessModel(state, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ keyguardRepository.setWakefulnessModel(model)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index d60d994..771c3e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -45,6 +46,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -60,6 +62,7 @@
private val authenticationInteractor =
utils.authenticationInteractor(
repository = authenticationRepository,
+ sceneInteractor = sceneInteractor,
)
private val keyguardRepository = utils.keyguardRepository
private val keyguardInteractor =
@@ -67,6 +70,7 @@
repository = keyguardRepository,
)
private val sysUiState: SysUiState = mock()
+ private val falsingCollector: FalsingCollector = mock()
private val underTest =
SceneContainerStartable(
@@ -78,6 +82,7 @@
sysUiState = sysUiState,
displayId = Display.DEFAULT_DISPLAY,
sceneLogger = mock(),
+ falsingCollector = falsingCollector,
)
@Test
@@ -243,7 +248,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -259,7 +264,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -275,7 +280,7 @@
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -294,11 +299,217 @@
authenticationRepository.setUnlocked(true)
runCurrent()
- keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
+ @Test
+ fun collectFalsingSignals_onSuccessfulUnlock() =
+ testScope.runTest {
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ verify(falsingCollector, never()).onSuccessfulUnlock()
+
+ // Move around scenes without unlocking.
+ listOf(
+ SceneKey.Shade,
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ )
+ .forEach { sceneKey ->
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ runCurrent()
+ verify(falsingCollector, never()).onSuccessfulUnlock()
+ }
+
+ // Changing to the Gone scene should report a successful unlock.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ runCurrent()
+ verify(falsingCollector).onSuccessfulUnlock()
+
+ // Move around scenes without changing back to Lockscreen, shouldn't report another
+ // unlock.
+ listOf(
+ SceneKey.Shade,
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.Gone,
+ )
+ .forEach { sceneKey ->
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ runCurrent()
+ verify(falsingCollector, times(1)).onSuccessfulUnlock()
+ }
+
+ // Changing to the Lockscreen scene shouldn't report a successful unlock.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+ runCurrent()
+ verify(falsingCollector, times(1)).onSuccessfulUnlock()
+
+ // Move around scenes without unlocking.
+ listOf(
+ SceneKey.Shade,
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ )
+ .forEach { sceneKey ->
+ sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+ runCurrent()
+ verify(falsingCollector, times(1)).onSuccessfulUnlock()
+ }
+
+ // Changing to the Gone scene should report a second successful unlock.
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ runCurrent()
+ verify(falsingCollector, times(2)).onSuccessfulUnlock()
+ }
+
+ @Test
+ fun collectFalsingSignals_setShowingAod() =
+ testScope.runTest {
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ verify(falsingCollector).setShowingAod(false)
+
+ keyguardRepository.setIsDozing(true)
+ runCurrent()
+ verify(falsingCollector).setShowingAod(true)
+
+ keyguardRepository.setIsDozing(false)
+ runCurrent()
+ verify(falsingCollector, times(2)).setShowingAod(false)
+ }
+
+ @Test
+ fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
+ testScope.runTest {
+ keyguardRepository.setAodAvailable(false)
+ runCurrent()
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ runCurrent()
+ verify(falsingCollector, times(1)).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+ runCurrent()
+ verify(falsingCollector, times(1)).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, times(1)).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ runCurrent()
+ verify(falsingCollector, times(1)).onScreenTurningOn()
+ verify(falsingCollector, times(1)).onScreenOnFromTouch()
+ verify(falsingCollector, times(1)).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+ runCurrent()
+ verify(falsingCollector, times(1)).onScreenTurningOn()
+ verify(falsingCollector, times(1)).onScreenOnFromTouch()
+ verify(falsingCollector, times(2)).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ runCurrent()
+ verify(falsingCollector, times(2)).onScreenTurningOn()
+ verify(falsingCollector, times(1)).onScreenOnFromTouch()
+ verify(falsingCollector, times(2)).onScreenOff()
+ }
+
+ @Test
+ fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
+ testScope.runTest {
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_TAP)
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(ASLEEP)
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+
+ keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE_FROM_POWER_BUTTON)
+ runCurrent()
+ verify(falsingCollector, never()).onScreenTurningOn()
+ verify(falsingCollector, never()).onScreenOnFromTouch()
+ verify(falsingCollector, never()).onScreenOff()
+ }
+
+ @Test
+ fun collectFalsingSignals_bouncerVisibility() =
+ testScope.runTest {
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ verify(falsingCollector).onBouncerHidden()
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+ runCurrent()
+ verify(falsingCollector).onBouncerShown()
+
+ sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+ runCurrent()
+ verify(falsingCollector, times(2)).onBouncerHidden()
+ }
+
private fun prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
@@ -334,11 +545,23 @@
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.POWER_BUTTON
)
- private val STARTING_TO_WAKE =
+ private val ASLEEP =
+ WakefulnessModel(
+ state = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON
+ )
+ private val STARTING_TO_WAKE_FROM_POWER_BUTTON =
WakefulnessModel(
state = WakefulnessState.STARTING_TO_WAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.POWER_BUTTON
)
+ private val STARTING_TO_WAKE_FROM_TAP =
+ WakefulnessModel(
+ state = WakefulnessState.STARTING_TO_WAKE,
+ lastWakeReason = WakeSleepReason.TAP,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 88abb642..0b56a59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -39,7 +39,8 @@
private val interactor = utils.sceneInteractor()
private val underTest =
SceneContainerViewModel(
- interactor = interactor,
+ sceneInteractor = interactor,
+ falsingInteractor = utils.falsingInteractor(),
)
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 209dcc1d..c573ac63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -716,7 +716,6 @@
mAmbientState,
mRecordingController,
mFalsingManager,
- new FalsingCollectorFake(),
mAccessibilityManager,
mLockscreenGestureLogger,
mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 1738b06..dfd782b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -63,6 +63,8 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.google.common.util.concurrent.MoreExecutors;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +75,7 @@
import org.mockito.Spy;
import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -98,6 +101,7 @@
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
+ private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
private float mPreferredRefreshRate = -1;
@@ -125,6 +129,7 @@
mConfigurationController,
mKeyguardViewMediator,
mKeyguardBypassController,
+ mBackgroundExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 3cce423..39fe498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -36,6 +36,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -56,6 +57,8 @@
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.DozeScrimController
+import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -90,6 +93,8 @@
@Mock private lateinit var view: NotificationShadeWindowView
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dozeServiceHost: DozeServiceHost
+ @Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var backActionInteractor: BackActionInteractor
@Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@@ -98,6 +103,7 @@
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -155,46 +161,49 @@
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
- lockscreenShadeTransitionController,
- FalsingCollectorFake(),
- sysuiStatusBarStateController,
- dockManager,
- notificationShadeDepthController,
- view,
- notificationPanelViewController,
- ShadeExpansionStateManager(),
- stackScrollLayoutController,
- statusBarKeyguardViewManager,
- statusBarWindowStateController,
- lockIconViewController,
- centralSurfaces,
- backActionInteractor,
- powerInteractor,
- notificationShadeWindowController,
- unfoldTransitionProgressProvider,
- keyguardUnlockAnimationController,
- notificationInsetsController,
- ambientState,
- shadeLogger,
- pulsingGestureListener,
- mLockscreenHostedDreamGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- mock(KeyguardMessageAreaController.Factory::class.java),
- keyguardTransitionInteractor,
- primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
- fakeClock,
- BouncerMessageInteractor(
- FakeBouncerMessageRepository(),
- mock(BouncerMessageFactory::class.java),
- FakeUserRepository(),
- CountDownTimerUtil(),
- featureFlags
- ),
- BouncerLogger(logcatLogBuffer("BouncerLog")),
- keyEventInteractor,
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ dozeServiceHost,
+ dozeScrimController,
+ backActionInteractor,
+ powerInteractor,
+ notificationShadeWindowController,
+ unfoldTransitionProgressProvider,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ shadeLogger,
+ dumpManager,
+ pulsingGestureListener,
+ mLockscreenHostedDreamGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ mock(KeyguardMessageAreaController.Factory::class.java),
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ notificationExpansionRepository,
+ featureFlags,
+ fakeClock,
+ BouncerMessageInteractor(
+ FakeBouncerMessageRepository(),
+ mock(BouncerMessageFactory::class.java),
+ FakeUserRepository(),
+ CountDownTimerUtil(),
+ featureFlags
+ ),
+ BouncerLogger(logcatLogBuffer("BouncerLog")),
+ keyEventInteractor,
)
underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 66d48d6..3031658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
+import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -56,6 +57,8 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.DozeScrimController
+import com.android.systemui.statusbar.phone.DozeServiceHost
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.window.StatusBarWindowStateController
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
@@ -89,6 +92,8 @@
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var shadeController: ShadeController
@Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dozeServiceHost: DozeServiceHost
+ @Mock private lateinit var dozeScrimController: DozeScrimController
@Mock private lateinit var backActionInteractor: BackActionInteractor
@Mock private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var dockManager: DockManager
@@ -107,6 +112,7 @@
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock private lateinit var ambientState: AmbientState
@Mock private lateinit var shadeLogger: ShadeLogger
+ @Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -174,6 +180,8 @@
statusBarWindowStateController,
lockIconViewController,
centralSurfaces,
+ dozeServiceHost,
+ dozeScrimController,
backActionInteractor,
powerInteractor,
notificationShadeWindowController,
@@ -182,6 +190,7 @@
notificationInsetsController,
ambientState,
shadeLogger,
+ dumpManager,
pulsingGestureListener,
mLockscreenHostedDreamGestureListener,
keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e22e571..ab0ae05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -37,7 +37,6 @@
import com.android.keyguard.TestScopeProvider;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
@@ -132,7 +131,6 @@
@Mock protected AmbientState mAmbientState;
@Mock protected RecordingController mRecordingController;
@Mock protected FalsingManager mFalsingManager;
- @Mock protected FalsingCollector mFalsingCollector;
@Mock protected AccessibilityManager mAccessibilityManager;
@Mock protected LockscreenGestureLogger mLockscreenGestureLogger;
@Mock protected MetricsLogger mMetricsLogger;
@@ -242,7 +240,6 @@
mAmbientState,
mRecordingController,
mFalsingManager,
- mFalsingCollector,
mAccessibilityManager,
mLockscreenGestureLogger,
mMetricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 6a14a00..bf2d6a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -20,20 +20,28 @@
import android.view.Display
import android.view.WindowManager
import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.IStatusBarService
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import org.junit.Before
import org.junit.Test
@@ -47,6 +55,8 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
+ private val executor = FakeExecutor(FakeSystemClock())
+
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -61,6 +71,17 @@
@Mock private lateinit var nswvc: NotificationShadeWindowViewController
@Mock private lateinit var display: Display
@Mock private lateinit var touchLog: LogBuffer
+ @Mock private lateinit var iStatusBarService: IStatusBarService
+ @Mock private lateinit var headsUpManager: HeadsUpManager
+
+ private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
+ WindowRootViewVisibilityInteractor(
+ TestScopeProvider.getTestScope(),
+ WindowRootViewVisibilityRepository(iStatusBarService, executor),
+ FakeKeyguardRepository(),
+ headsUpManager,
+ )
+ }
private lateinit var shadeController: ShadeControllerImpl
@@ -74,6 +95,7 @@
commandQueue,
FakeExecutor(FakeSystemClock()),
touchLog,
+ windowRootViewVisibilityInteractor,
keyguardStateController,
statusBarStateController,
statusBarKeyguardViewManager,
@@ -86,6 +108,7 @@
Lazy { gutsManager },
)
shadeController.setNotificationShadeWindowViewController(nswvc)
+ shadeController.setVisibilityListener(mock())
}
@Test
@@ -133,4 +156,24 @@
// VERIFY that cancelCurrentTouch is NOT called
verify(nswvc, never()).cancelCurrentTouch()
}
+
+ @Test
+ fun visible_changesToTrue_windowInteractorUpdated() {
+ shadeController.makeExpandedVisible(true)
+
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+ }
+
+ @Test
+ fun visible_changesToFalse_windowInteractorUpdated() {
+ // GIVEN the shade is currently expanded
+ shadeController.makeExpandedVisible(true)
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isTrue()
+
+ // WHEN the shade is collapsed
+ shadeController.collapseShade()
+
+ // THEN the interactor is notified
+ assertThat(windowRootViewVisibilityInteractor.isLockscreenOrShadeVisible.value).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 5fa6b3a..e7056c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -58,6 +58,7 @@
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel;
import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.kotlin.FlowProviderKt;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import com.android.systemui.utils.os.FakeHandler;
@@ -72,6 +73,8 @@
import java.util.Arrays;
import java.util.List;
+import kotlinx.coroutines.flow.MutableStateFlow;
+
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -105,7 +108,8 @@
private ShadeCarrier mShadeCarrier3;
private TestableLooper mTestableLooper;
@Mock
- private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
+ private ShadeCarrierGroupController.OnSingleCarrierChangedListener
+ mOnSingleCarrierChangedListener;
@Mock
private MobileUiAdapter mMobileUiAdapter;
@Mock
@@ -119,6 +123,9 @@
@Mock
private StatusBarPipelineFlags mStatusBarPipelineFlags;
+ private final MutableStateFlow<Boolean> mIsVisibleFlow =
+ FlowProviderKt.getMutableStateFlow(true);
+
private FakeSlotIndexResolver mSlotIndexResolver;
private ClickListenerTextView mNoCarrierTextView;
@@ -170,7 +177,7 @@
mMobileUiAdapter,
mMobileContextProvider,
mStatusBarPipelineFlags
- )
+ )
.setShadeCarrierGroup(mShadeCarrierGroup)
.build();
@@ -181,6 +188,7 @@
when(mStatusBarPipelineFlags.useNewShadeCarrierGroupMobileIcons()).thenReturn(true);
when(mMobileContextProvider.getMobileContextForSub(anyInt(), any())).thenReturn(mContext);
when(mMobileIconsViewModel.getLogger()).thenReturn(mMobileViewLogger);
+ when(mShadeCarrierGroupMobileIconViewModel.isVisible()).thenReturn(mIsVisibleFlow);
when(mMobileIconsViewModel.viewModelForSub(anyInt(), any()))
.thenReturn(mShadeCarrierGroupMobileIconViewModel);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
index 786856b..66d2465 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt
@@ -20,6 +20,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State.CONNECTED
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.privacy.PrivacyItemController
@@ -105,5 +106,7 @@
suspend fun emit(value: ConnectedDisplayInteractor.State) = flow.emit(value)
override val connectedDisplayState: Flow<ConnectedDisplayInteractor.State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = MutableSharedFlow<PendingDisplay>()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 6be2fa5..4fcccf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -93,9 +93,6 @@
fakeFeatureFlags
)
- // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
- systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
-
// StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
.thenReturn(android.util.Pair(10, 10))
@@ -156,6 +153,21 @@
assertEquals(0f, batteryChip.view.alpha)
}
+ /** Regression test for b/294104969. */
+ @Test
+ fun testPrivacyStatusEvent_beforeSystemUptime_stillDisplayed() = runTest {
+ initializeSystemStatusAnimationScheduler(testScope = this, advancePastMinUptime = false)
+
+ // WHEN the uptime hasn't quite passed the minimum required uptime...
+ systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME / 2)
+
+ // BUT the event is a privacy event
+ createAndScheduleFakePrivacyEvent()
+
+ // THEN the privacy event still happens
+ assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+ }
+
@Test
fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
// Instantiate class under test with TestScope from runTest
@@ -568,7 +580,10 @@
return batteryChip
}
- private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+ private fun initializeSystemStatusAnimationScheduler(
+ testScope: TestScope,
+ advancePastMinUptime: Boolean = true,
+ ) {
systemStatusAnimationScheduler =
SystemStatusAnimationSchedulerImpl(
systemEventCoordinator,
@@ -581,5 +596,10 @@
)
// add a mock listener
systemStatusAnimationScheduler.addCallback(listener)
+
+ if (advancePastMinUptime) {
+ // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+ systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 7117c23..bbf0151 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -40,8 +40,14 @@
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.shared.model.WakeSleepReason;
+import com.android.systemui.keyguard.shared.model.WakefulnessModel;
+import com.android.systemui.keyguard.shared.model.WakefulnessState;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
@@ -54,7 +60,9 @@
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.google.android.collect.Lists;
@@ -71,6 +79,8 @@
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
+import kotlinx.coroutines.test.TestScope;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -89,7 +99,7 @@
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NotificationListener mListener;
- @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
+ @Mock private HeadsUpManager mHeadsUpManager;
private NotificationEntry mEntry;
private TestableNotificationLogger mLogger;
@@ -97,12 +107,23 @@
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
new NotificationPanelLoggerFake();
+ private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
+ private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+ private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
+ mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+ mTestScope.getBackgroundScope(),
+ new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
+ mKeyguardRepository,
+ mHeadsUpManager);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -120,10 +141,12 @@
mVisibilityProvider,
mNotifPipeline,
mock(StatusBarStateControllerImpl.class),
- mShadeExpansionStateManager,
+ mWindowRootViewVisibilityInteractor,
+ mJavaAdapter,
mBarService,
mExpansionStateLogger
);
+ mLogger.start();
mLogger.setUpWithContainer(mListContainer);
verify(mNotifPipeline).addCollectionListener(any());
}
@@ -183,31 +206,26 @@
Mockito.reset(mBarService);
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
- mLogger.onDozingChanged(true); // And go back to sleep, turning off logging
+
+ setStateAwake(); // Wake to lockscreen
+
+ setStateAsleep(); // And go back to sleep, turning off logging
mUiBgExecutor.runAllReady();
+
// The visibility objects are recycled by NotificationLogger, so we can't use specific
// matchers here.
verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
}
- private void setStateAsleep() {
- mLogger.onShadeExpansionFullyChanged(true);
- mLogger.onDozingChanged(true);
- mLogger.onStateChanged(StatusBarState.KEYGUARD);
- }
-
- private void setStateAwake() {
- mLogger.onShadeExpansionFullyChanged(false);
- mLogger.onDozingChanged(false);
- mLogger.onStateChanged(StatusBarState.SHADE);
- }
-
@Test
- public void testLogPanelShownOnWake() {
+ public void testLogPanelShownOnWakeToLockscreen() {
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
+
+ // Wake to lockscreen
+ mLogger.onStateChanged(StatusBarState.KEYGUARD);
+ setStateAwake();
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen);
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -222,9 +240,14 @@
@Test
public void testLogPanelShownOnShadePull() {
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
+ // Start as awake, but with the panel not visible
setStateAwake();
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+
// Now expand panel
- mLogger.onShadeExpansionFullyChanged(true);
+ mLogger.onStateChanged(StatusBarState.SHADE);
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
@@ -251,13 +274,34 @@
when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry));
setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
+
+ // Wake to lockscreen
+ setStateAwake();
+
assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
assertEquals(0, n.instanceId);
}
+ private void setStateAsleep() {
+ mKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER));
+ mTestScope.getTestScheduler().runCurrent();
+ }
+
+ private void setStateAwake() {
+ mKeyguardRepository.setWakefulnessModel(
+ new WakefulnessModel(
+ WakefulnessState.AWAKE,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER));
+ mTestScope.getTestScheduler().runCurrent();
+ }
+
private class TestableNotificationLogger extends NotificationLogger {
TestableNotificationLogger(NotificationListener notificationListener,
@@ -266,7 +310,8 @@
NotificationVisibilityProvider visibilityProvider,
NotifPipeline notifPipeline,
StatusBarStateControllerImpl statusBarStateController,
- ShadeExpansionStateManager shadeExpansionStateManager,
+ WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
+ JavaAdapter javaAdapter,
IStatusBarService barService,
ExpansionStateLogger expansionStateLogger) {
super(
@@ -276,7 +321,8 @@
visibilityProvider,
notifPipeline,
statusBarStateController,
- shadeExpansionStateManager,
+ windowRootViewVisibilityInteractor,
+ javaAdapter,
expansionStateLogger,
mNotificationPanelLoggerFake
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 0cc0b98..7c8199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -27,7 +27,6 @@
import com.android.internal.logging.MetricsLogger
import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.FalsingManager
@@ -57,6 +56,7 @@
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.SystemClock
import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
import junit.framework.Assert
import org.junit.After
import org.junit.Before
@@ -68,7 +68,6 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
-import java.util.Optional
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -100,7 +99,6 @@
private val gutsManager: NotificationGutsManager = mock()
private val onUserInteractionCallback: OnUserInteractionCallback = mock()
private val falsingManager: FalsingManager = mock()
- private val falsingCollector: FalsingCollector = mock()
private val featureFlags: FeatureFlags = mock()
private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
private val bubblesManager: BubblesManager = mock()
@@ -140,7 +138,6 @@
/*allowLongPress=*/ false,
onUserInteractionCallback,
falsingManager,
- falsingCollector,
featureFlags,
peopleNotificationIdentifier,
Optional.of(bubblesManager),
@@ -226,20 +223,20 @@
fun registerSettingsListener_forBubbles() {
controller.init(mock(NotificationEntry::class.java))
val viewStateObserver = withArgCaptor {
- verify(view).addOnAttachStateChangeListener(capture());
+ verify(view).addOnAttachStateChangeListener(capture())
}
- viewStateObserver.onViewAttachedToWindow(view);
- verify(settingsController).addCallback(any(), any());
+ viewStateObserver.onViewAttachedToWindow(view)
+ verify(settingsController).addCallback(any(), any())
}
@Test
fun unregisterSettingsListener_forBubbles() {
controller.init(mock(NotificationEntry::class.java))
val viewStateObserver = withArgCaptor {
- verify(view).addOnAttachStateChangeListener(capture());
+ verify(view).addOnAttachStateChangeListener(capture())
}
- viewStateObserver.onViewDetachedFromWindow(view);
- verify(settingsController).removeCallback(any(), any());
+ viewStateObserver.onViewDetachedFromWindow(view)
+ verify(settingsController).removeCallback(any(), any())
}
@Test
@@ -263,11 +260,17 @@
whenever(view.privateLayout).thenReturn(childView)
controller.mSettingsListener.onSettingChanged(
- BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
+ BUBBLES_SETTING_URI,
+ view.entry.sbn.userId,
+ "1"
+ )
verify(childView).setBubblesEnabledForUser(true)
controller.mSettingsListener.onSettingChanged(
- BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
+ BUBBLES_SETTING_URI,
+ view.entry.sbn.userId,
+ "9"
+ )
verify(childView).setBubblesEnabledForUser(false)
}
@@ -277,13 +280,12 @@
whenever(view.privateLayout).thenReturn(childView)
val notification = Notification.Builder(mContext).build()
- val sbn = SbnBuilder().setNotification(notification)
- .setUser(UserHandle.of(USER_ALL))
- .build()
- whenever(view.entry).thenReturn(NotificationEntryBuilder()
- .setSbn(sbn)
- .setUser(UserHandle.of(USER_ALL))
- .build())
+ val sbn =
+ SbnBuilder().setNotification(notification).setUser(UserHandle.of(USER_ALL)).build()
+ whenever(view.entry)
+ .thenReturn(
+ NotificationEntryBuilder().setSbn(sbn).setUser(UserHandle.of(USER_ALL)).build()
+ )
controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
verify(childView).setBubblesEnabledForUser(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 705d52b..9e0f83c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -37,6 +37,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,11 +67,16 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -84,6 +90,9 @@
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wmshell.BubblesManager;
import org.junit.Before;
@@ -97,6 +106,8 @@
import java.util.Optional;
+import kotlinx.coroutines.test.TestScope;
+
/**
* Tests for {@link NotificationGutsManager}.
*/
@@ -108,6 +119,10 @@
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+
+ private TestScope mTestScope = TestScopeProvider.getTestScope();
+ private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
private Handler mHandler;
private NotificationTestHelper mHelper;
@@ -124,6 +139,7 @@
@Mock private AccessibilityManager mAccessibilityManager;
@Mock private HighPriorityProvider mHighPriorityProvider;
@Mock private INotificationManager mINotificationManager;
+ @Mock private IStatusBarService mBarService;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
@Mock private ChannelEditorDialogController mChannelEditorDialogController;
@@ -140,6 +156,8 @@
@Mock private UserManager mUserManager;
+ private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
+
@Before
public void setUp() {
mTestableLooper = TestableLooper.get(this);
@@ -148,21 +166,42 @@
mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this));
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
- mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
+ mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor(
+ mTestScope.getBackgroundScope(),
+ new WindowRootViewVisibilityRepository(mBarService, mExecutor),
+ new FakeKeyguardRepository(),
+ mHeadsUpManagerPhone);
+
+ mGutsManager = new NotificationGutsManager(
+ mContext,
+ mHandler,
+ mHandler,
+ mJavaAdapter,
mAccessibilityManager,
- mHighPriorityProvider, mINotificationManager, mUserManager,
- mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
- mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
- Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
+ mHighPriorityProvider,
+ mINotificationManager,
+ mUserManager,
+ mPeopleSpaceWidgetManager,
+ mLauncherApps,
+ mShortcutManager,
+ mChannelEditorDialogController,
+ mContextTracker,
+ mAssistantFeedbackController,
+ Optional.of(mBubblesManager),
+ new UiEventLoggerFake(),
+ mOnUserInteractionCallback,
mShadeController,
+ mWindowRootViewVisibilityInteractor,
mNotificationLockscreenUserManager,
mStatusBarStateController,
mDeviceProvisionedController,
mMetricsLogger,
- mHeadsUpManagerPhone, mActivityStarter);
+ mHeadsUpManagerPhone,
+ mActivityStarter);
mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+ mGutsManager.start();
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -210,6 +249,62 @@
}
@Test
+ public void testLockscreenShadeVisible_visible_gutsNotClosed() {
+ // First, start out lockscreen or shade as not visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ NotificationGuts guts = mock(NotificationGuts.class);
+ mGutsManager.setExposedGuts(guts);
+
+ // WHEN the lockscreen or shade becomes visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the guts are not closed
+ verify(guts, never()).removeCallbacks(any());
+ verify(guts, never()).closeControls(
+ anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testLockscreenShadeVisible_notVisible_gutsClosed() {
+ // First, start out lockscreen or shade as visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ NotificationGuts guts = mock(NotificationGuts.class);
+ mGutsManager.setExposedGuts(guts);
+
+ // WHEN the lockscreen or shade is no longer visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the guts are closed
+ verify(guts).removeCallbacks(any());
+ verify(guts).closeControls(
+ /* leavebehinds= */ eq(true),
+ /* controls= */ eq(true),
+ /* x= */ anyInt(),
+ /* y= */ anyInt(),
+ /* force= */ eq(true));
+ }
+
+ @Test
+ public void testLockscreenShadeVisible_notVisible_listContainerReset() {
+ // First, start out lockscreen or shade as visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // WHEN the lockscreen or shade is no longer visible
+ mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(false);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN the list container is reset
+ verify(mNotificationListContainer).resetExposedMenuView(anyBoolean(), anyBoolean());
+ }
+
+ @Test
public void testChangeDensityOrFontScale() {
NotificationGuts guts = spy(new NotificationGuts(mContext));
when(guts.post(any())).thenAnswer(invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 0425830..9dfcb3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -54,7 +54,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.TestableDependency;
-import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
@@ -600,7 +599,6 @@
mock(OnExpandClickListener.class),
mock(ExpandableNotificationRow.CoordinateOnClickListener.class),
new FalsingManagerFake(),
- new FalsingCollectorFake(),
mStatusBarStateController,
mPeopleNotificationIdentifier,
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f47efe3..4e3690f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -24,7 +24,6 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -62,7 +61,6 @@
import android.os.IThermalService;
import android.os.Looper;
import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.service.dreams.IDreamManager;
import android.support.test.metricshelper.MetricsAsserts;
@@ -73,13 +71,7 @@
import android.util.SparseArray;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
-import android.window.BackEvent;
-import android.window.OnBackAnimationCallback;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
-import android.window.WindowOnBackInvokedDispatcher;
import androidx.test.filters.SmallTest;
@@ -125,6 +117,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
@@ -158,7 +151,6 @@
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
@@ -167,13 +159,10 @@
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -199,7 +188,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -207,6 +195,8 @@
import java.io.PrintWriter;
import java.util.Optional;
+import javax.inject.Provider;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -227,7 +217,6 @@
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private NotificationStackScrollLayout mStackScroller;
@Mock private NotificationStackScrollLayoutController mStackScrollerController;
- @Mock private NotificationListContainer mNotificationListContainer;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private ShadeLogger mShadeLogger;
@@ -254,7 +243,6 @@
@Mock private StatusBarNotificationPresenter mNotificationPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
- @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private StatusBarSignalPolicy mStatusBarSignalPolicy;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@@ -273,6 +261,7 @@
@Mock private DynamicPrivacyController mDynamicPrivacyController;
@Mock private AutoHideController mAutoHideController;
@Mock private StatusBarWindowController mStatusBarWindowController;
+ @Mock private Provider<CollapsedStatusBarFragment> mCollapsedStatusBarFragmentProvider;
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock private UserSwitcherController mUserSwitcherController;
@Mock private Bubbles mBubbles;
@@ -291,8 +280,6 @@
@Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@Mock private VolumeComponent mVolumeComponent;
@Mock private CommandQueue mCommandQueue;
- @Mock private CentralSurfacesComponent.Factory mStatusBarComponentFactory;
- @Mock private CentralSurfacesComponent mCentralSurfacesComponent;
@Mock private CentralSurfacesCommandQueueCallbacks mCentralSurfacesCommandQueueCallbacks;
@Mock private PluginManager mPluginManager;
@Mock private ViewMediatorCallback mViewMediatorCallback;
@@ -325,18 +312,11 @@
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private CameraLauncher mCameraLauncher;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
- /**
- * The process of registering/unregistering a predictive back callback requires a
- * ViewRootImpl, which is present IRL, but may be missing during a Mockito unit test.
- * To prevent an NPE during test execution, we explicitly craft and provide a fake ViewRootImpl.
- */
- @Mock private ViewRootImpl mViewRootImpl;
- @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
@Mock private UserTracker mUserTracker;
@Mock private FingerprintManager mFingerprintManager;
- @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
@Mock IPowerManager mPowerManagerService;
@Mock ActivityStarter mActivityStarter;
+ @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -388,18 +368,6 @@
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
mMetricsLogger = new FakeMetricsLogger();
- NotificationLogger notificationLogger = new NotificationLogger(
- mNotificationListener,
- mUiBgExecutor,
- mNotifLiveDataStore,
- mVisibilityProvider,
- mock(NotifPipeline.class),
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mExpansionStateLogger,
- new NotificationPanelLoggerFake()
- );
- notificationLogger.setVisibilityReporter(mock(Runnable.class));
when(mCommandQueue.asBinder()).thenReturn(new Binder());
@@ -438,7 +406,6 @@
when(mNotificationShadeWindowViewControllerLazy.get())
.thenReturn(mNotificationShadeWindowViewController);
- when(mStatusBarComponentFactory.create()).thenReturn(mCentralSurfacesComponent);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
@@ -448,6 +415,7 @@
mCommandQueue,
mMainExecutor,
mock(LogBuffer.class),
+ mock(WindowRootViewVisibilityInteractor.class),
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
@@ -475,7 +443,10 @@
mock(FragmentService.class),
mLightBarController,
mAutoHideController,
- new StatusBarInitializer(mStatusBarWindowController, emptySet()),
+ new StatusBarInitializer(
+ mStatusBarWindowController,
+ mCollapsedStatusBarFragmentProvider,
+ emptySet()),
mStatusBarWindowController,
mStatusBarWindowStateController,
mKeyguardUpdateMonitor,
@@ -490,7 +461,6 @@
new FalsingCollectorFake(),
mBroadcastDispatcher,
mNotificationGutsManager,
- notificationLogger,
mNotificationInterruptStateProvider,
new ShadeExpansionStateManager(),
mKeyguardViewMediator,
@@ -537,10 +507,10 @@
mDozeScrimController,
mVolumeComponent,
mCommandQueue,
- mStatusBarComponentFactory,
() -> mCentralSurfacesCommandQueueCallbacks,
mPluginManager,
mShadeController,
+ mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
mViewMediatorCallback,
mInitController,
@@ -578,16 +548,9 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter
- ) {
- @Override
- protected ViewRootImpl getViewRootImpl() {
- return mViewRootImpl;
- }
- };
+ );
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
- when(mViewRootImpl.getOnBackInvokedDispatcher())
- .thenReturn(mOnBackInvokedDispatcher);
when(mKeyguardViewMediator.registerCentralSurfaces(
any(CentralSurfacesImpl.class),
any(NotificationPanelViewController.class),
@@ -609,7 +572,6 @@
PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
- notificationLogger.setUpWithContainer(mNotificationListContainer);
mCentralSurfaces.registerCallbacks();
}
@@ -799,151 +761,6 @@
}
@Test
- public void testLogHidden() {
- try {
- mCentralSurfaces.handleVisibleToUserChanged(false);
- mUiBgExecutor.runAllReady();
- verify(mBarService, times(1)).onPanelHidden();
- verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
- } catch (RemoteException e) {
- fail();
- }
- }
-
- /**
- * Do the following:
- * 1. verify that a predictive back callback is registered when CSurf becomes visible
- * 2. verify that the same callback is unregistered when CSurf becomes invisible
- */
- @Test
- public void testPredictiveBackCallback_registration() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- mCentralSurfaces.handleVisibleToUserChanged(false);
- verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
- eq(mOnBackInvokedCallback.getValue()));
- }
-
- /**
- * Do the following:
- * 1. capture the predictive back callback during registration
- * 2. call the callback directly
- * 3. verify that the ShadeController's panel collapse animation is invoked
- */
- @Test
- public void testPredictiveBackCallback_invocationCollapsesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- mOnBackInvokedCallback.getValue().onBackInvoked();
- verify(mBackActionInteractor).onBackRequested();
- }
-
- /**
- * When back progress is at 100%, the onBackProgressed animation driver inside
- * NotificationPanelViewController should be invoked appropriately (with 1.0f passed in).
- */
- @Test
- public void testPredictiveBackAnimation_progressMaxScalesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- OnBackAnimationCallback onBackAnimationCallback =
- (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
- BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 100.0f, 1.0f, BackEvent.EDGE_LEFT);
- onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
- verify(mNotificationPanelViewController).onBackProgressed(eq(1.0f));
- }
-
- /**
- * When back progress is at 0%, the onBackProgressed animation driver inside
- * NotificationPanelViewController should be invoked appropriately (with 0.0f passed in).
- */
- @Test
- public void testPredictiveBackAnimation_progressMinScalesPanel() {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
- eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
- mOnBackInvokedCallback.capture());
-
- OnBackAnimationCallback onBackAnimationCallback =
- (OnBackAnimationCallback) (mOnBackInvokedCallback.getValue());
- when(mBackActionInteractor.shouldBackBeHandled()).thenReturn(true);
- when(mNotificationPanelViewController.canBeCollapsed()).thenReturn(true);
-
- BackEvent fakeSwipeInFromLeftEdge = new BackEvent(20.0f, 10.0f, 0.0f, BackEvent.EDGE_LEFT);
- onBackAnimationCallback.onBackProgressed(fakeSwipeInFromLeftEdge);
- verify(mNotificationPanelViewController).onBackProgressed(eq(0.0f));
- }
-
- @Test
- public void testPanelOpenForHeadsUp() {
- when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(true);
- mCentralSurfaces.setBarStateForTest(SHADE);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(false, 1);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
- public void testPanelOpenAndClear() {
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
-
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
- mCentralSurfaces.setBarStateForTest(SHADE);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(true, 5);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
- public void testPanelOpenAndNoClear() {
- when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
- when(mNotificationsController.getActiveNotificationsCount()).thenReturn(5);
- when(mNotificationPresenter.isPresenterFullyCollapsed()).thenReturn(false);
- mCentralSurfaces.setBarStateForTest(StatusBarState.KEYGUARD);
-
- try {
- mCentralSurfaces.handleVisibleToUserChanged(true);
- mUiBgExecutor.runAllReady();
- verify(mBarService, never()).onPanelHidden();
- verify(mBarService, times(1)).onPanelRevealed(false, 5);
- } catch (RemoteException e) {
- fail();
- }
- mMainExecutor.runAllReady();
- }
-
- @Test
public void testDump_DoesNotCrash() {
mCentralSurfaces.dump(new PrintWriter(new ByteArrayOutputStream()), null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
index 9795b9d..71c27de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
+import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -316,5 +317,7 @@
suspend fun emit(value: State) = flow.emit(value)
override val connectedDisplayState: Flow<State>
get() = flow
+ override val pendingDisplay: Flow<PendingDisplay?>
+ get() = TODO("Not yet implemented")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
index 08e89fb..6f04f36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt
@@ -220,30 +220,6 @@
/** Regression test for b/255428281. */
@Test
- fun internalAndExternalIconWithSameName_internalRemoved_viaRemoveAll_externalStays() {
- val slotName = "mute"
-
- // Internal
- underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription")
-
- // External
- underTest.setIconFromTile(slotName, createExternalIcon())
-
- // WHEN the internal icon is removed via #removeAllIconsForSlot
- underTest.removeAllIconsForSlot(slotName)
-
- // THEN the internal icon is removed but the external icon remains
- assertThat(iconList.slots).hasSize(2)
- assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX)
- assertThat(iconList.slots[1].name).isEqualTo(slotName)
- assertThat(iconList.slots[0].hasIconsInSlot()).isTrue()
- assertThat(iconList.slots[1].hasIconsInSlot()).isFalse() // Indicates removal
-
- verify(iconGroup).onRemoveIcon(1)
- }
-
- /** Regression test for b/255428281. */
- @Test
fun internalAndExternalIconWithSameName_internalUpdatedIndependently() {
val slotName = "mute"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 085ec27..0ff6f20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -120,34 +120,6 @@
verify(manager, never()).onRemoveIcon(anyInt());
}
- @Test
- public void testRemoveAllIconsForSlot_ignoredForNewPipeline() {
- IconManager manager = mock(IconManager.class);
-
- // GIVEN the new pipeline is on
- StatusBarPipelineFlags flags = mock(StatusBarPipelineFlags.class);
- when(flags.isIconControlledByFlags("test_icon")).thenReturn(true);
-
- StatusBarIconController iconController = new StatusBarIconControllerImpl(
- mContext,
- mock(CommandQueue.class),
- mock(DemoModeController.class),
- mock(ConfigurationController.class),
- mock(TunerService.class),
- mock(DumpManager.class),
- mock(StatusBarIconList.class),
- flags
- );
-
- iconController.addIconGroup(manager);
-
- // WHEN a request to remove a new icon is sent
- iconController.removeAllIconsForSlot("test_icon");
-
- // THEN it is not removed for those icons
- verify(manager, never()).onRemoveIcon(anyInt());
- }
-
private <T extends IconManager & TestableIconManager> void testCallOnAdd_forManager(T manager) {
StatusBarIconHolder holder = holderForType(TYPE_ICON);
manager.onIconAdded(0, "test_slot", false, holder);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
index 8150a31..6624ec2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.qs.tileimpl.QSTileImpl.ResourceIcon
+import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -41,7 +42,6 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
-import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.util.CarrierConfigTracker
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -144,17 +144,112 @@
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(networkModel)
- // Type is [Visible] since that is the only model that stores a resId
- val expectedIcon: WifiIcon.Visible =
- WifiIcon.fromModel(networkModel, context) as WifiIcon.Visible
-
assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
assertThat(latest?.secondaryLabel).isNull()
- assertThat(latest?.icon).isEqualTo(ResourceIcon.get(expectedIcon.icon.res))
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
assertThat(latest?.iconId).isNull()
}
@Test
+ fun wifiDefaultAndActive_hotspotNone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4]))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotTablet() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_tablet))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotLaptop() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_laptop))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotWatch() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_watch))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotAuto() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_auto))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotPhone() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotUnknown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotInvalid() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.tileModel)
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+ assertThat(latest?.icon)
+ .isEqualTo(ResourceIcon.get(com.android.settingslib.R.drawable.ic_hotspot_phone))
+ }
+
+ @Test
fun wifiDefaultAndNotActive_noNetworksAvailable() =
testScope.runTest {
val latest by collectLastValue(underTest.tileModel)
@@ -237,6 +332,20 @@
assertThat(latest?.icon).isNull()
}
+ private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = hotspot,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ }
+
companion object {
const val SUB_1_ID = 1
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 5aacc66..a520f6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -22,6 +22,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.connectivity.WifiIcons
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -117,6 +118,27 @@
}
@Test
+ fun wifiIcon_validHotspot_hotspotIconNotShown() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.wifiIcon)
+
+ // Even WHEN the network has a valid hotspot type
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = true,
+ level = 1,
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
+ )
+ )
+
+ // THEN the hotspot icon is not used for the status bar icon, and the typical wifi icon
+ // is used instead
+ assertThat(latest).isInstanceOf(WifiIcon.Visible::class.java)
+ assertThat((latest as WifiIcon.Visible).res).isEqualTo(WifiIcons.WIFI_FULL_ICONS[1])
+ }
+
+ @Test
fun activity_showActivityConfigFalse_outputsFalse() =
testScope.runTest {
whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
new file mode 100644
index 0000000..274acc9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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.kotlin
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Wrapper for flow constructors that can be retrieved from java tests */
+fun <T> getMutableStateFlow(value: T): MutableStateFlow<T> = MutableStateFlow(value)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7ebf6c8..1b623a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -341,6 +341,7 @@
mConfigurationController,
mKeyguardViewMediator,
mKeyguardBypassController,
+ syncExecutor,
mColorExtractor,
mDumpManager,
mKeyguardStateController,
@@ -419,6 +420,7 @@
syncExecutor,
mock(Handler.class),
mTaskViewTransitions,
+ mTransitions,
mock(SyncTransactionQueue.class),
mock(IWindowManager.class),
mBubbleProperties);
@@ -510,6 +512,11 @@
}
@Test
+ public void instantiateController_registerTransitionObserver() {
+ verify(mTransitions).registerObserver(any());
+ }
+
+ @Test
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1469,6 +1476,34 @@
}
@Test
+ public void testBroadcastReceiverCloseDialogs_reasonHomeKey() {
+ spyOn(mContext);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleData.setExpanded(true);
+
+ verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ i.putExtra("reason", "homekey");
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ assertStackCollapsed();
+ }
+
+ @Test
+ public void testBroadcastReceiverCloseDialogs_reasonRecentsKey() {
+ spyOn(mContext);
+ mBubbleController.updateBubble(mBubbleEntry);
+ mBubbleData.setExpanded(true);
+
+ verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ i.putExtra("reason", "recentapps");
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ assertStackCollapsed();
+ }
+
+ @Test
public void testBroadcastReceiver_screenOff() {
spyOn(mContext);
mBubbleController.updateBubble(mBubbleEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 5855347..9ad234e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -43,6 +43,7 @@
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -74,6 +75,7 @@
ShellExecutor shellMainExecutor,
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
+ Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
BubbleProperties bubbleProperties) {
@@ -82,7 +84,8 @@
windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
- new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties);
+ new SyncExecutor(), taskViewTransitions, transitions,
+ syncQueue, wmService, bubbleProperties);
setInflateSynchronously(true);
onInit();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 715d661..6f51d1b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -33,10 +33,17 @@
/** Fake [DisplayRepository] implementation for testing. */
class FakeDisplayRepository : DisplayRepository {
private val flow = MutableSharedFlow<Set<Display>>()
+ private val pendingDisplayFlow = MutableSharedFlow<Int?>()
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
+ /** Emits [value] as [pendingDisplayId] flow value. */
+ suspend fun emit(value: Int?) = pendingDisplayFlow.emit(value)
+
override val displays: Flow<Set<Display>>
get() = flow
+
+ override val pendingDisplayId: Flow<Int?>
+ get() = pendingDisplayFlow
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index faebcaa..cc0c943 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -81,7 +81,7 @@
override val linearDozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
- override val statusBarState: Flow<StatusBarState> = _statusBarState
+ override val statusBarState: StateFlow<StatusBarState> = _statusBarState
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 4f33a97..f7db44e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -27,6 +27,9 @@
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
@@ -98,6 +101,9 @@
private val context = test.context
+ private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
+ private var falsingInteractor: FalsingInteractor? = null
+
fun fakeSceneContainerRepository(
containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
): SceneContainerRepository {
@@ -175,6 +181,7 @@
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
featureFlags = featureFlags,
+ falsingInteractor = falsingInteractor(),
)
}
@@ -191,6 +198,14 @@
)
}
+ fun falsingInteractor(collector: FalsingCollector = falsingCollector()): FalsingInteractor {
+ return falsingInteractor ?: FalsingInteractor(collector).also { falsingInteractor = it }
+ }
+
+ fun falsingCollector(): FalsingCollector {
+ return falsingCollectorFake
+ }
+
private fun applicationScope(): CoroutineScope {
return testScope.backgroundScope
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 03e3423..3774d1d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -84,10 +84,6 @@
}
@Override
- public void removeAllIconsForSlot(String slot) {
- }
-
- @Override
public void setIconAccessibilityLiveRegion(String slot, int mode) {
}
diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS
new file mode 100644
index 0000000..c66443f
--- /dev/null
+++ b/packages/services/VirtualCamera/OWNERS
@@ -0,0 +1,3 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
+caen@google.com
+jsebechlebsky@google.com
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index f3a540b..cd83f8f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -389,11 +389,19 @@
@Override
public void onMotionEvent(MotionEvent transformedEvent, MotionEvent rawEvent,
int policyFlags) {
+ if (!mInstalled) {
+ Slog.w(TAG, "onMotionEvent called before input filter installed!");
+ return;
+ }
sendInputEvent(transformedEvent, policyFlags);
}
@Override
public void onKeyEvent(KeyEvent event, int policyFlags) {
+ if (!mInstalled) {
+ Slog.w(TAG, "onKeyEvent called before input filter installed!");
+ return;
+ }
sendInputEvent(event, policyFlags);
}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4688658..423b85f 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -56,7 +56,7 @@
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
- private final FillServiceCallbacks mCallbacks;
+ private FillServiceCallbacks mCallbacks;
private final Object mLock = new Object();
private CompletableFuture<FillResponse> mPendingFillRequest;
private int mPendingFillRequestId = INVALID_REQUEST_ID;
@@ -128,9 +128,12 @@
*/
public int cancelCurrentRequest() {
synchronized (mLock) {
- return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
+ int canceledRequestId = mPendingFillRequest != null && mPendingFillRequest.cancel(false)
? mPendingFillRequestId
: INVALID_REQUEST_ID;
+ mPendingFillRequest = null;
+ mPendingFillRequestId = INVALID_REQUEST_ID;
+ return canceledRequestId;
}
}
@@ -184,6 +187,10 @@
mPendingFillRequest = null;
mPendingFillRequestId = INVALID_REQUEST_ID;
}
+ if (mCallbacks == null) {
+ Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+ return;
+ }
if (err == null) {
mCallbacks.onFillRequestSuccess(request.getId(), res,
mComponentName.getPackageName(), request.getFlags());
@@ -220,6 +227,10 @@
return save;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> Handler.getMain().post(() -> {
+ if (mCallbacks == null) {
+ Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+ return;
+ }
if (err == null) {
mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res);
} else {
@@ -234,6 +245,8 @@
}
public void destroy() {
+ cancelCurrentRequest();
unbind();
+ mCallbacks = null;
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index b573800..3b02be5 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -1750,12 +1750,8 @@
synchronized (mClearDataLock) {
mClearingData = true;
- try {
- mActivityManager.clearApplicationUserData(packageName, keepSystemState, observer,
- mUserId);
- } catch (RemoteException e) {
- // can't happen because the activity manager is in this process
- }
+ mActivityManagerInternal.clearApplicationUserData(packageName, keepSystemState,
+ /*isRestore=*/ true, observer, mUserId);
// Only wait 30 seconds for the clear data to happen.
long timeoutMark = System.currentTimeMillis() + CLEAR_DATA_TIMEOUT_INTERVAL;
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 4d249ab..a787644 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -32,6 +32,7 @@
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.IInstalld.IFsveritySetupAuthToken;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.storage.OnObbStateChangeListener.ERROR_ALREADY_MOUNTED;
import static android.os.storage.OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT;
@@ -678,6 +679,7 @@
private static final int H_VOLUME_STATE_CHANGED = 15;
private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
+ private static final int H_REMOUNT_VOLUMES_ON_MOVE = 18;
class StorageManagerServiceHandler extends Handler {
public StorageManagerServiceHandler(Looper looper) {
@@ -833,6 +835,10 @@
}
break;
}
+ case H_REMOUNT_VOLUMES_ON_MOVE: {
+ remountVolumesForRunningUsersOnMove();
+ break;
+ }
}
}
}
@@ -1286,6 +1292,44 @@
}
}
+ /**
+ * This method informs vold and storaged that the user has stopped and started whenever move
+ * storage is performed. This ensures that the correct emulated volumes are mounted for the
+ * users other than the current user. This solves an edge case wherein the correct emulated
+ * volumes are not mounted, this will cause the media data to be still stored on internal
+ * storage whereas the data should be stored in the adopted primary storage. This method stops
+ * the users at vold first which will remove the old volumes which and starts the users at vold
+ * which will reattach the correct volumes. This does not performs a full reset as full reset
+ * clears every state from vold and SMS {@link #resetIfRebootedAndConnected} which is expensive
+ * and causes instability.
+ */
+ private void remountVolumesForRunningUsersOnMove() {
+ // Do not want to hold the lock for long
+ final List<Integer> unlockedUsers = new ArrayList<>();
+ synchronized (mLock) {
+ for (int userId : mSystemUnlockedUsers) {
+ if (userId == mCurrentUserId) continue;
+ unlockedUsers.add(userId);
+ }
+ }
+ for (Integer userId : unlockedUsers) {
+ try {
+ mVold.onUserStopped(userId);
+ mStoraged.onUserStopped(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ for (Integer userId : unlockedUsers) {
+ try {
+ mVold.onUserStarted(userId);
+ mStoraged.onUserStarted(userId);
+ } catch (Exception e) {
+ Slog.wtf(TAG, e);
+ }
+ }
+ }
+
private boolean supportsBlockCheckpoint() throws RemoteException {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
return mVold.supportsBlockCheckpoint();
@@ -1820,6 +1864,7 @@
mPrimaryStorageUuid = mMoveTargetUuid;
writeSettingsLocked();
+ mHandler.obtainMessage(H_REMOUNT_VOLUMES_ON_MOVE).sendToTarget();
}
if (PackageManager.isMoveStatusFinished(status)) {
@@ -4640,7 +4685,7 @@
pw.print(") total size: ");
pw.print(pair.second);
pw.print(" (");
- pw.print(DataUnit.MEBIBYTES.toBytes(pair.second));
+ pw.print(pair.second / DataUnit.MEBIBYTES.toBytes(1L));
pw.println(" MiB)");
}
@@ -4950,5 +4995,24 @@
}
}
+ @Override
+ public IFsveritySetupAuthToken createFsveritySetupAuthToken(ParcelFileDescriptor authFd,
+ int appUid, @UserIdInt int userId) throws IOException {
+ try {
+ return mInstaller.createFsveritySetupAuthToken(authFd, appUid, userId);
+ } catch (Installer.InstallerException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public int enableFsverity(IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws IOException {
+ try {
+ return mInstaller.enableFsverity(authToken, filePath, packageName);
+ } catch (Installer.InstallerException e) {
+ throw new IOException(e);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 03022b0..cd0a9d2 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -74,6 +74,15 @@
{
"name": "CtsVcnTestCases",
"file_patterns": ["VcnManagementService\\.java"]
+ },
+ {
+ "name": "FrameworksNetTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
],
"presubmit-large": [
@@ -91,6 +100,21 @@
}
],
"file_patterns": ["ClipboardService\\.java"]
+ },
+ {
+ "name": "CtsHostsideNetworkTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5773e20..594712f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3531,6 +3531,12 @@
@Override
public boolean clearApplicationUserData(final String packageName, boolean keepState,
final IPackageDataObserver observer, int userId) {
+ return clearApplicationUserData(packageName, keepState, /*isRestore=*/ false, observer,
+ userId);
+ }
+
+ private boolean clearApplicationUserData(final String packageName, boolean keepState,
+ boolean isRestore, final IPackageDataObserver observer, int userId) {
enforceNotIsolatedCaller("clearApplicationUserData");
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
@@ -3625,6 +3631,9 @@
intent.putExtra(Intent.EXTRA_UID,
(appInfo != null) ? appInfo.uid : INVALID_UID);
intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+ if (isRestore) {
+ intent.putExtra(Intent.EXTRA_IS_RESTORE, true);
+ }
if (isInstantApp) {
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
}
@@ -3843,15 +3852,16 @@
@Override
public void forceStopPackage(final String packageName, int userId) {
- forceStopPackage(packageName, userId, /*flags=*/ 0);
+ forceStopPackage(packageName, userId, /*flags=*/ 0, null);
}
@Override
public void forceStopPackageEvenWhenStopping(final String packageName, int userId) {
- forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED);
+ forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED, null);
}
- private void forceStopPackage(final String packageName, int userId, int userRunningFlags) {
+ private void forceStopPackage(final String packageName, int userId, int userRunningFlags,
+ String reason) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: forceStopPackage() from pid="
@@ -3896,7 +3906,8 @@
+ packageName + ": " + e);
}
if (mUserController.isUserRunning(user, userRunningFlags)) {
- forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
+ forceStopPackageLocked(packageName, pkgUid,
+ reason == null ? ("from pid " + callingPid) : reason);
finishForceStopPackageLocked(packageName, pkgUid);
}
}
@@ -14889,8 +14900,8 @@
Intent.EXTRA_QUARANTINED, false);
if (suspended && quarantined && packageNames != null) {
for (int i = 0; i < packageNames.length; i++) {
- forceStopPackageLocked(packageNames[i], -1, false, true, true,
- false, false, userId, "suspended");
+ forceStopPackage(packageNames[i], userId,
+ ActivityManager.FLAG_OR_STOPPED, "quarantined");
}
}
@@ -19014,6 +19025,13 @@
return mAppProfiler.mCachedAppsWatermarkData.getCachedAppsHighWatermarkStats(
atomTag, resetAfterPull);
}
+
+ @Override
+ public boolean clearApplicationUserData(final String packageName, boolean keepState,
+ boolean isRestore, final IPackageDataObserver observer, int userId) {
+ return ActivityManagerService.this.clearApplicationUserData(packageName, keepState,
+ isRestore, observer, userId);
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 1a58556..5fa0ffa 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1514,7 +1514,7 @@
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
pid,
(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
- new Pair<String, Integer>(app.processName, reason)));
+ new Pair<ProcessRecord, Integer>(app, reason)));
}
}
@@ -2159,11 +2159,12 @@
case REPORT_UNFREEZE_MSG: {
int pid = msg.arg1;
int frozenDuration = msg.arg2;
- Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
- String processName = obj.first;
+ Pair<ProcessRecord, Integer> obj = (Pair<ProcessRecord, Integer>) msg.obj;
+ ProcessRecord app = obj.first;
+ String processName = app.processName;
int reason = obj.second;
- reportUnfreeze(pid, frozenDuration, processName, reason);
+ reportUnfreeze(app, pid, frozenDuration, processName, reason);
} break;
case UID_FROZEN_STATE_CHANGED_MSG: {
final boolean frozen = (msg.arg1 == 1);
@@ -2358,10 +2359,11 @@
}
}
- private void reportUnfreeze(int pid, int frozenDuration, String processName,
- @UnfreezeReason int reason) {
+ private void reportUnfreeze(ProcessRecord app, int pid, int frozenDuration,
+ String processName, @UnfreezeReason int reason) {
EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
+ app.onProcessUnfrozen();
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 5ad49a4..db74f1a 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -225,6 +225,32 @@
mBaseProcessTracker = baseProcessTracker;
}
+ void onProcessFrozen() {
+ synchronized (mService.mProcessStats.mLock) {
+ final ProcessState tracker = mBaseProcessTracker;
+ if (tracker != null) {
+ final PackageList pkgList = mApp.getPkgList();
+ final long now = SystemClock.uptimeMillis();
+ synchronized (pkgList) {
+ tracker.onProcessFrozen(now, pkgList.getPackageListLocked());
+ }
+ }
+ }
+ }
+
+ void onProcessUnfrozen() {
+ synchronized (mService.mProcessStats.mLock) {
+ final ProcessState tracker = mBaseProcessTracker;
+ if (tracker != null) {
+ final PackageList pkgList = mApp.getPkgList();
+ final long now = SystemClock.uptimeMillis();
+ synchronized (pkgList) {
+ tracker.onProcessUnfrozen(now, pkgList.getPackageListLocked());
+ }
+ }
+ }
+ }
+
void onProcessActive(IApplicationThread thread, ProcessStatsService tracker) {
if (mThread == null) {
synchronized (mProfilerLock) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e2edd8a..cfbb5a5 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1359,6 +1359,15 @@
return false;
}
+ void onProcessFrozen() {
+ mProfile.onProcessFrozen();
+ }
+
+ void onProcessUnfrozen() {
+ mProfile.onProcessUnfrozen();
+ }
+
+
/*
* Delete all packages from list except the package indicated in info
*/
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 68062b5..65f6af7 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsAppOpsTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
},
@@ -31,7 +31,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 6d42da6..c05a1f6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -772,7 +772,6 @@
final int mVolume;
final boolean mIsLeOutput;
final @NonNull String mEventSource;
- final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
final int mAudioSystemDevice;
final int mMusicDevice;
@@ -787,7 +786,6 @@
mEventSource = d.mEventSource;
mAudioSystemDevice = audioDevice;
mMusicDevice = AudioSystem.DEVICE_NONE;
- mCodec = codec;
}
// constructor used by AudioDeviceBroker to search similar message
@@ -796,7 +794,6 @@
mProfile = profile;
mEventSource = "";
mMusicDevice = AudioSystem.DEVICE_NONE;
- mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
mAudioSystemDevice = 0;
mState = 0;
mSupprNoisy = false;
@@ -811,7 +808,6 @@
mProfile = profile;
mEventSource = "";
mMusicDevice = musicDevice;
- mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
mAudioSystemDevice = audioSystemDevice;
mState = state;
mSupprNoisy = false;
@@ -829,7 +825,6 @@
mEventSource = src.mEventSource;
mAudioSystemDevice = src.mAudioSystemDevice;
mMusicDevice = src.mMusicDevice;
- mCodec = src.mCodec;
}
// redefine equality op so we can match messages intended for this device
@@ -847,6 +842,19 @@
}
return false;
}
+
+ @Override
+ public String toString() {
+ return "BtDeviceInfo: device=" + mDevice.toString()
+ + " state=" + mState
+ + " prof=" + mProfile
+ + " supprNoisy=" + mSupprNoisy
+ + " volume=" + mVolume
+ + " isLeOutput=" + mIsLeOutput
+ + " eventSource=" + mEventSource
+ + " audioSystemDevice=" + mAudioSystemDevice
+ + " musicDevice=" + mMusicDevice;
+ }
}
BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device,
@@ -859,9 +867,6 @@
break;
case BluetoothProfile.A2DP:
audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
- synchronized (mDeviceStateLock) {
- codec = mBtHelper.getA2dpCodec(device);
- }
break;
case BluetoothProfile.HEARING_AID:
audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID;
@@ -1696,8 +1701,11 @@
case MSG_L_SET_BT_ACTIVE_DEVICE:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
- mDeviceInventory.onSetBtActiveDevice(btInfo,
+ final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getA2dpCodecWithFallbackToSBC(
+ btInfo.mDevice, "MSG_L_SET_BT_ACTIVE_DEVICE");
+ mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
(btInfo.mProfile
!= BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
? mAudioService.getBluetoothContextualVolumeStream()
@@ -1730,12 +1738,16 @@
(String) msg.obj, msg.arg1);
}
break;
- case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
+ case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: {
+ final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
synchronized (mDeviceStateLock) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getA2dpCodecWithFallbackToSBC(
+ btInfo.mDevice, "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE");
mDeviceInventory.onBluetoothDeviceConfigChange(
- (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+ btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
}
- break;
+ } break;
case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
onSendBecomingNoisyIntent();
break;
@@ -1831,20 +1843,12 @@
}
break;
case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
- final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
- if (info.mDevice == null) break;
+ final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj;
+ if (btInfo.mDevice == null) break;
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "msg: onBluetoothActiveDeviceChange "
- + " state=" + info.mState
- // only querying address as this is the only readily available
- // field on the device
- + " addr=" + info.mDevice.getAddress()
- + " prof=" + info.mProfile
- + " supprNoisy=" + info.mSupprNoisy
- + " src=" + info.mEventSource
- )).printLog(TAG));
+ "msg: onBluetoothActiveDeviceChange " + btInfo)).printLog(TAG));
synchronized (mDeviceStateLock) {
- mDeviceInventory.setBluetoothActiveDevice(info);
+ mDeviceInventory.setBluetoothActiveDevice(btInfo);
}
} break;
case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index da89502..60af280 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -503,9 +503,11 @@
}
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
+ int streamType) {
if (AudioService.DEBUG_DEVICES) {
Log.d(TAG, "onSetBtActiveDevice"
+ " btDevice=" + btInfo.mDevice
@@ -518,10 +520,7 @@
}
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
- + " addr=" + address
- + " profile=" + btInfo.mProfile
- + " state=" + btInfo.mState
- + " codec=" + AudioSystem.audioFormatToString(btInfo.mCodec)));
+ + btInfo + " codec=" + AudioSystem.audioFormatToString(codec)));
new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice")
.set(MediaMetrics.Property.STATUS, btInfo.mProfile)
@@ -529,7 +528,7 @@
AudioSystem.getDeviceName(btInfo.mAudioSystemDevice))
.set(MediaMetrics.Property.ADDRESS, address)
.set(MediaMetrics.Property.ENCODING,
- AudioSystem.audioFormatToString(btInfo.mCodec))
+ AudioSystem.audioFormatToString(codec))
.set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice")
.set(MediaMetrics.Property.STREAM_TYPE,
AudioSystem.streamToString(streamType))
@@ -568,7 +567,7 @@
btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
"onSetBtActiveDevice");
}
- makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
+ makeA2dpDeviceAvailable(btInfo, codec, "onSetBtActiveDevice");
}
break;
case BluetoothProfile.HEARING_AID:
@@ -594,9 +593,10 @@
}
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ void onBluetoothDeviceConfigChange(
- @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
+ @NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onBluetoothDeviceConfigChange")
.set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
@@ -610,7 +610,6 @@
Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
}
int volume = btInfo.mVolume;
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
String address = btDevice.getAddress();
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -639,8 +638,7 @@
}
mmi.set(MediaMetrics.Property.ADDRESS, address)
- .set(MediaMetrics.Property.ENCODING,
- AudioSystem.audioFormatToString(audioCodec))
+ .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
.set(MediaMetrics.Property.INDEX, volume)
.set(MediaMetrics.Property.NAME, di.mDeviceName);
@@ -648,20 +646,19 @@
if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
boolean a2dpCodecChange = false;
if (btInfo.mProfile == BluetoothProfile.A2DP) {
- if (di.mDeviceCodecFormat != audioCodec) {
- di.mDeviceCodecFormat = audioCodec;
+ if (di.mDeviceCodecFormat != codec) {
+ di.mDeviceCodecFormat = codec;
mConnectedDevices.replace(key, di);
a2dpCodecChange = true;
}
final int res = mAudioSystem.handleDeviceConfigChange(
- btInfo.mAudioSystemDevice, address,
- BtHelper.getName(btDevice), audioCodec);
+ btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange failed for A2DP device addr="
+ address + " codec="
- + AudioSystem.audioFormatToString(audioCodec))
+ + AudioSystem.audioFormatToString(codec))
.printLog(TAG));
// force A2DP device disconnection in case of error so that AudioService
@@ -672,7 +669,7 @@
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange success for A2DP device addr="
+ address
- + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+ + " codec=" + AudioSystem.audioFormatToString(codec))
.printLog(TAG));
}
@@ -1338,7 +1335,7 @@
* @param device the device whose connection state is queried
* @return true if connected
*/
- // called with AudioDeviceBroker.mDeviceStateLock lock held
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
@@ -1489,7 +1486,7 @@
}
}
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ void onBtProfileDisconnected(int profile) {
switch (profile) {
case BluetoothProfile.HEADSET:
@@ -1554,7 +1551,7 @@
disconnectLeAudio(AudioSystem.DEVICE_OUT_BLE_BROADCAST);
}
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
private void disconnectHeadset() {
boolean disconnect = false;
synchronized (mDevicesLock) {
@@ -1594,9 +1591,10 @@
return mCurAudioRoutes;
}
- // only public for mocking/spying
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
- @VisibleForTesting
+ /**
+ * Set a Bluetooth device to active.
+ */
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) {
int delay;
synchronized (mDevicesLock) {
@@ -1617,12 +1615,7 @@
}
if (AudioService.DEBUG_DEVICES) {
- Log.i(TAG, "setBluetoothActiveDevice device: " + info.mDevice
- + " profile: " + BluetoothProfile.getProfileName(info.mProfile)
- + " state: " + BluetoothProfile.getConnectionStateName(info.mState)
- + " delay(ms): " + delay
- + " codec:" + Integer.toHexString(info.mCodec)
- + " suppressNoisyIntent: " + info.mSupprNoisy);
+ Log.i(TAG, "setBluetoothActiveDevice " + info.toString() + " delay(ms): " + delay);
}
mDeviceBroker.postBluetoothActiveDevice(info, delay);
if (info.mProfile == BluetoothProfile.HEARING_AID
@@ -1658,10 +1651,10 @@
@GuardedBy("mDevicesLock")
private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
String eventSource) {
final String address = btInfo.mDevice.getAddress();
final String name = BtHelper.getName(btInfo.mDevice);
- final int a2dpCodec = btInfo.mCodec;
// enable A2DP before notifying A2DP connection to avoid unnecessary processing in
// audio policy manager
@@ -1671,7 +1664,7 @@
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
+ AudioSystem.DEVICE_STATE_AVAILABLE, codec);
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
@@ -1694,7 +1687,7 @@
// time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
- address, a2dpCodec, sensorUuid);
+ address, codec, sensorUuid);
final String diKey = di.getKey();
mConnectedDevices.put(diKey, di);
// on a connection always overwrite the device seen by AudioPolicy, see comment above when
@@ -1910,9 +1903,9 @@
}
@GuardedBy("mDevicesLock")
- private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+ private void makeA2dpDeviceUnavailableNow(String address, int codec) {
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
- .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
+ .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(codec))
.set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
if (address == null) {
@@ -1939,7 +1932,7 @@
AudioDeviceAttributes ada = new AudioDeviceAttributes(
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
final int res = mAudioSystem.setDeviceConnectionState(ada,
- AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
+ AudioSystem.DEVICE_STATE_UNAVAILABLE, codec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 1462b3c..6af409e 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -173,8 +173,8 @@
//----------------------------------------------------------------------
// Interface for AudioDeviceBroker
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void onSystemReady() {
mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
resetBluetoothSco();
@@ -263,8 +263,20 @@
return AudioSystem.bluetoothCodecToAudioFormat(btCodecConfig.getCodecType());
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec
+ int getA2dpCodecWithFallbackToSBC(
+ @NonNull BluetoothDevice device, @NonNull String source) {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec int codec = getA2dpCodec(device);
+ if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "getA2dpCodec DEFAULT from " + source + " fallback to SBC"));
+ return AudioSystem.AUDIO_FORMAT_SBC;
+ }
+ return codec;
+ }
+
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void onReceiveBtEvent(Intent intent) {
final String action = intent.getAction();
@@ -283,8 +295,8 @@
* Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT
* as part of the serialization of the communication route selection
*/
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
private void onScoAudioStateChanged(int state) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
@@ -355,16 +367,16 @@
== BluetoothHeadset.STATE_AUDIO_CONNECTED;
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
@NonNull String eventSource) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
@@ -435,8 +447,8 @@
mScoConnectionState = state;
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -445,7 +457,8 @@
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void onBtProfileDisconnected(int profile) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile " + BluetoothProfile.getProfileName(profile) + " disconnected"));
@@ -474,7 +487,8 @@
}
}
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
@@ -522,8 +536,8 @@
}
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
private void onHeadsetProfileConnected(BluetoothHeadset headset) {
// Discard timeout message
mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -642,8 +656,8 @@
return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress();
}
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
/*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
+ " -> " + getAnonymizedAddress(btDevice));
@@ -712,8 +726,8 @@
//----------------------------------------------------------------------
- // @GuardedBy("AudioDeviceBroker.mSetModeLock")
- //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
+ // @GuardedBy("mDeviceBroker.mSetModeLock")
+ // @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
@GuardedBy("BtHelper.this")
private boolean requestScoState(int state, int scoAudioMode) {
checkScoAudioState();
diff --git a/services/core/java/com/android/server/audio/UuidUtils.java b/services/core/java/com/android/server/audio/UuidUtils.java
index 2003619..035bea3 100644
--- a/services/core/java/com/android/server/audio/UuidUtils.java
+++ b/services/core/java/com/android/server/audio/UuidUtils.java
@@ -45,26 +45,24 @@
* Generate a headtracking UUID from AudioDeviceAttributes
*/
public static UUID uuidFromAudioDeviceAttributes(AudioDeviceAttributes device) {
- switch (device.getInternalType()) {
- case AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP:
- String address = device.getAddress().replace(":", "");
- if (address.length() != 12) {
- return null;
- }
- address = "0x" + address;
- long lsb = LSB_PREFIX_BT;
- try {
- lsb |= Long.decode(address).longValue();
- } catch (NumberFormatException e) {
- return null;
- }
- if (AudioService.DEBUG_DEVICES) {
- Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
- }
- return new UUID(0, lsb);
- default:
- // Handle other device types here
- return null;
+ if (!AudioSystem.isBluetoothA2dpOutDevice(device.getInternalType())
+ && !AudioSystem.isBluetoothLeOutDevice(device.getInternalType())) {
+ return null;
}
+ String address = device.getAddress().replace(":", "");
+ if (address.length() != 12) {
+ return null;
+ }
+ address = "0x" + address;
+ long lsb = LSB_PREFIX_BT;
+ try {
+ lsb |= Long.decode(address).longValue();
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ if (AudioService.DEBUG_DEVICES) {
+ Slog.i(TAG, "uuidFromAudioDeviceAttributes lsb: " + Long.toHexString(lsb));
+ }
+ return new UUID(0, lsb);
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
index e109cc8..707240b 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStats.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -90,6 +90,11 @@
mRejectedAttempts = 0;
}
+ /** Update enrollment notification counter after sending a notification. */
+ public void updateNotificationCounter() {
+ mEnrollmentNotifications++;
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 3d1b162..e8a20de 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -68,8 +68,10 @@
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+
if (userId != UserHandle.USER_NULL
&& intent.getAction().equals(Intent.ACTION_USER_REMOVED)) {
+ Slog.d(TAG, "Removing data for user: " + userId);
onUserRemoved(userId);
}
}
@@ -84,7 +86,9 @@
mModality = modality;
mBiometricNotification = biometricNotification;
- context.registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_USER_REMOVED));
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ context.registerReceiver(mBroadcastReceiver, intentFilter);
}
private void initializeUserAuthenticationStatsMap() {
@@ -121,10 +125,11 @@
authenticationStats.authenticate(authenticated);
+ sendNotificationIfNeeded(userId);
+
if (mPersisterInitialized) {
persistDataIfNeeded(userId);
}
- sendNotificationIfNeeded(userId);
}
/** Check if a notification should be sent after a calculation cycle. */
@@ -164,8 +169,10 @@
}
if (hasEnrolledFace && !hasEnrolledFingerprint) {
mBiometricNotification.sendFpEnrollNotification(mContext);
+ authenticationStats.updateNotificationCounter();
} else if (!hasEnrolledFace && hasEnrolledFingerprint) {
mBiometricNotification.sendFaceEnrollNotification(mContext);
+ authenticationStats.updateNotificationCounter();
}
}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
index 74e1410..8122b1d 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsPersister.java
@@ -59,7 +59,7 @@
// The package info in the context isn't initialized in the way it is for normal apps,
// so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
// build the path manually below using the same policy that appears in ContextImpl.
- final File prefsFile = new File(Environment.getDataSystemDeDirectory(), FILE_NAME);
+ final File prefsFile = new File(Environment.getDataSystemDirectory(), FILE_NAME);
mSharedPreferences = context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
@@ -137,16 +137,19 @@
iterator.remove();
break;
}
+ // Reset frrStatJson when user doesn't exist.
+ frrStatJson = null;
}
- // If there's existing frr stats in the file, we want to update the stats for the given
- // modality and keep the stats for other modalities.
+ // Checks if this is a new user and there's no JSON for this user in the storage.
if (frrStatJson == null) {
frrStatJson = new JSONObject().put(USER_ID, userId);
}
frrStatsSet.add(buildFrrStats(frrStatJson, totalAttempts, rejectedAttempts,
enrollmentNotifications, modality));
+ Slog.d(TAG, "frrStatsSet to persist: " + frrStatsSet);
+
mSharedPreferences.edit().putStringSet(KEY, frrStatsSet).apply();
} catch (JSONException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 2ff695d..f1c74f0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -78,7 +78,8 @@
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
- FACE_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+ Notification.CATEGORY_SYSTEM, FACE_RE_ENROLL_NOTIFICATION_TAG,
+ Notification.VISIBILITY_SECRET);
}
/**
@@ -95,15 +96,14 @@
final Intent intent = new Intent(FACE_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
- FACE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+ Notification.CATEGORY_RECOMMENDATION, FACE_ENROLL_NOTIFICATION_TAG,
+ Notification.VISIBILITY_PUBLIC);
}
/**
@@ -120,16 +120,14 @@
final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent,
- FINGERPRINT_ENROLL_CHANNEL, FINGERPRINT_ENROLL_NOTIFICATION_TAG,
- Notification.VISIBILITY_PUBLIC);
+ Notification.CATEGORY_RECOMMENDATION, FINGERPRINT_ENROLL_CHANNEL,
+ FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
}
/**
@@ -163,13 +161,13 @@
null /* options */, UserHandle.CURRENT);
showNotificationHelper(context, name, title, content, pendingIntent,
- FINGERPRINT_BAD_CALIBRATION_CHANNEL, BAD_CALIBRATION_NOTIFICATION_TAG,
- Notification.VISIBILITY_SECRET);
+ Notification.CATEGORY_SYSTEM, FINGERPRINT_BAD_CALIBRATION_CHANNEL,
+ BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
}
private static void showNotificationHelper(Context context, String name, String title,
- String content, PendingIntent pendingIntent, String channelName,
- String notificationTag, int visibility) {
+ String content, PendingIntent pendingIntent, String category,
+ String channelName, String notificationTag, int visibility) {
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
final NotificationChannel channel = new NotificationChannel(channelName, name,
@@ -182,7 +180,7 @@
.setOnlyAlertOnce(true)
.setLocalOnly(true)
.setAutoCancel(true)
- .setCategory(Notification.CATEGORY_SYSTEM)
+ .setCategory(category)
.setContentIntent(pendingIntent)
.setVisibility(visibility)
.build();
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
new file mode 100644
index 0000000..687d4b0
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -0,0 +1,30 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksNetTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsHostsideNetworkTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cba5039..ff35b19 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3296,13 +3296,6 @@
}
agentConnect(this::onValidationStatus);
return; // Link properties are already sent.
- } else {
- // Underlying networks also set in agentConnect()
- doSetUnderlyingNetworks(networkAgent, Collections.singletonList(network));
- mNetworkCapabilities =
- new NetworkCapabilities.Builder(mNetworkCapabilities)
- .setUnderlyingNetworks(Collections.singletonList(network))
- .build();
}
lp = makeLinkProperties(); // Accesses VPN instance fields; must be locked
@@ -3384,8 +3377,6 @@
final LinkProperties oldLp = makeLinkProperties();
- final boolean underlyingNetworkHasChanged =
- !Arrays.equals(mConfig.underlyingNetworks, new Network[]{network});
mConfig.underlyingNetworks = new Network[] {network};
mConfig.mtu = calculateVpnMtu();
@@ -3417,18 +3408,9 @@
removed.getAddress(), removed.getPrefixLength());
}
} else {
- // Put below 3 updates into else block is because agentConnect() will do
- // those things, so there is no need to do the redundant work.
+ // Put below update into else block is because agentConnect() will do
+ // the same things, so there is no need to do the redundant work.
if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp);
- if (underlyingNetworkHasChanged) {
- mNetworkCapabilities =
- new NetworkCapabilities.Builder(mNetworkCapabilities)
- .setUnderlyingNetworks(
- Collections.singletonList(network))
- .build();
- doSetUnderlyingNetworks(mNetworkAgent,
- Collections.singletonList(network));
- }
}
}
@@ -3554,10 +3536,28 @@
*/
private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
if (underlyingNetwork == null) {
+ // For null underlyingNetwork case, there will not be a NetworkAgent available so
+ // no underlying network update is necessary here. Note that updating
+ // mNetworkCapabilities here would also be reasonable, but it will be updated next
+ // time the VPN connects anyway.
Log.d(TAG, "There is no active network for starting an IKE session");
return;
}
+ final List<Network> networks = Collections.singletonList(underlyingNetwork);
+ // Update network capabilities if underlying network is changed.
+ if (!networks.equals(mNetworkCapabilities.getUnderlyingNetworks())) {
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder(mNetworkCapabilities)
+ .setUnderlyingNetworks(networks)
+ .build();
+ // No NetworkAgent case happens when Vpn tries to start a new VPN. The underlying
+ // network update will be done later with NetworkAgent connected event.
+ if (mNetworkAgent != null) {
+ doSetUnderlyingNetworks(mNetworkAgent, networks);
+ }
+ }
+
if (maybeMigrateIkeSessionAndUpdateVpnTransportInfo(underlyingNetwork)) return;
startIkeSession(underlyingNetwork);
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
index f6d06aa..955b8d9 100644
--- a/services/core/java/com/android/server/display/WifiDisplayController.java
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -1062,8 +1062,10 @@
}
private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+ WifiP2pWfdInfo wfdInfo = device.getWfdInfo();
+ boolean isSessionAvailable = wfdInfo != null && wfdInfo.isSessionAvailable();
return new WifiDisplay(device.deviceAddress, device.deviceName, null,
- true, device.getWfdInfo().isSessionAvailable(), false);
+ true, isSessionAvailable, false);
}
private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 39b8bfd..36adea7 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -73,6 +73,7 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetdUtils.ModifyOperation;
import com.android.net.module.util.PermissionUtils;
@@ -782,7 +783,10 @@
@Override
public boolean getIpForwardingEnabled() throws IllegalStateException{
PermissionUtils.enforceNetworkStackPermission(mContext);
-
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#getIpForwardingEnabled not supported in V+");
+ }
try {
return mNetdService.ipfwdEnabled();
} catch (RemoteException | ServiceSpecificException e) {
@@ -793,7 +797,10 @@
@Override
public void setIpForwardingEnabled(boolean enable) {
PermissionUtils.enforceNetworkStackPermission(mContext);
- try {
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#setIpForwardingEnabled not supported in V+");
+ } try {
if (enable) {
mNetdService.ipfwdEnableForwarding("tethering");
} else {
@@ -807,6 +814,9 @@
@Override
public void startTethering(String[] dhcpRange) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#startTethering not supported in V+");
+ }
try {
NetdUtils.tetherStart(mNetdService, true /* usingLegacyDnsProxy */, dhcpRange);
} catch (RemoteException | ServiceSpecificException e) {
@@ -817,6 +827,9 @@
@Override
public void stopTethering() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#stopTethering not supported in V+");
+ }
try {
mNetdService.tetherStop();
} catch (RemoteException | ServiceSpecificException e) {
@@ -827,6 +840,9 @@
@Override
public boolean isTetheringStarted() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#isTetheringStarted not supported in V+");
+ }
try {
return mNetdService.tetherIsEnabled();
} catch (RemoteException | ServiceSpecificException e) {
@@ -837,6 +853,9 @@
@Override
public void tetherInterface(String iface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#tetherInterface not supported in V+");
+ }
try {
final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
@@ -849,6 +868,9 @@
@Override
public void untetherInterface(String iface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#untetherInterface not supported in V+");
+ }
try {
NetdUtils.untetherInterface(mNetdService, iface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -859,6 +881,10 @@
@Override
public String[] listTetheredInterfaces() {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException(
+ "NMS#listTetheredInterfaces not supported in V+");
+ }
try {
return mNetdService.tetherInterfaceList();
} catch (RemoteException | ServiceSpecificException e) {
@@ -869,6 +895,9 @@
@Override
public void enableNat(String internalInterface, String externalInterface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#enableNat not supported in V+");
+ }
try {
mNetdService.tetherAddForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
@@ -879,6 +908,9 @@
@Override
public void disableNat(String internalInterface, String externalInterface) {
PermissionUtils.enforceNetworkStackPermission(mContext);
+ if (SdkLevel.isAtLeastV()) {
+ throw new UnsupportedOperationException("NMS#disableNat not supported in V+");
+ }
try {
mNetdService.tetherRemoveForward(internalInterface, externalInterface);
} catch (RemoteException | ServiceSpecificException e) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index be6133b..1afa3ed 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -118,7 +118,6 @@
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -533,6 +532,8 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
private static final long NOTIFICATION_LOG_ASSISTANT_CANCEL = 195579280L;
+ private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
+
private IActivityManager mAm;
private ActivityTaskManagerInternal mAtm;
private ActivityManager mActivityManager;
@@ -6676,22 +6677,14 @@
}
private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
- if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)
- && Binder.withCleanCallingIdentity(
- () -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, false))) {
- // The package probably doesn't have WAKE_LOCK permission and should not require it.
- return Binder.withCleanCallingIdentity(() -> {
- WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- "NotificationManagerService:post:" + pkg);
- wakeLock.setWorkSource(new WorkSource(uid, pkg));
- // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
- wakeLock.acquire(30_000);
- return mPostNotificationTrackerFactory.newTracker(wakeLock);
- });
- } else {
- return mPostNotificationTrackerFactory.newTracker(null);
- }
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ wakeLock.acquire(POST_WAKE_LOCK_TIMEOUT.toMillis());
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
}
/**
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index e490745..a700d32 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1101,8 +1101,11 @@
.allowAlarms(true)
.allowMedia(true)
.build());
- } else {
+ } else if (rule.zenPolicy != null) {
policy.apply(rule.zenPolicy);
+ } else {
+ // active rule with no specified policy inherits the default settings
+ policy.apply(mConfig.toZenPolicy());
}
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 66a1703..b8feb4d 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -20,12 +20,10 @@
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
import static android.content.pm.PackageManager.DELETE_SUCCEEDED;
import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
-import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
@@ -66,20 +64,20 @@
import com.android.internal.util.Preconditions;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
import com.android.server.wm.ActivityTaskManagerInternal;
import dalvik.system.VMRuntime;
-import java.util.Collections;
import java.util.List;
/**
* Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called
* from a failed installation. Fixes user state after deletion.
* Handles special treatments to system apps.
- * Relies on RemovePackageHelper to clear internal data structures.
+ * Relies on RemovePackageHelper to clear internal data structures and remove app data.
*/
final class DeletePackageHelper {
private static final boolean DEBUG_CLEAN_APKS = false;
@@ -90,24 +88,17 @@
private final UserManagerInternal mUserManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final RemovePackageHelper mRemovePackageHelper;
- private final AppDataHelper mAppDataHelper;
// TODO(b/198166813): remove PMS dependency
- DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper,
- AppDataHelper appDataHelper) {
+ DeletePackageHelper(PackageManagerService pm, RemovePackageHelper removePackageHelper) {
mPm = pm;
mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mRemovePackageHelper = removePackageHelper;
- mAppDataHelper = appDataHelper;
}
DeletePackageHelper(PackageManagerService pm) {
- mPm = pm;
- mAppDataHelper = new AppDataHelper(mPm);
- mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
- mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
- mRemovePackageHelper = new RemovePackageHelper(mPm, mAppDataHelper);
+ this(pm, new RemovePackageHelper(pm));
}
/**
@@ -454,7 +445,7 @@
// semantics than normal for uninstalling system apps.
final boolean clearPackageStateAndReturn;
synchronized (mPm.mLock) {
- markPackageUninstalledForUserLPw(ps, user);
+ markPackageUninstalledForUserLPw(ps, user, flags);
if (!systemApp) {
// Do not uninstall the APK if an app should be cached
boolean keepUninstalledPackage =
@@ -484,7 +475,7 @@
}
}
if (clearPackageStateAndReturn) {
- clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+ mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags);
mPm.scheduleWritePackageRestrictions(user);
return;
}
@@ -531,55 +522,6 @@
}
}
- private void clearPackageStateForUserLIF(PackageSetting ps, int userId,
- PackageRemovedInfo outInfo, int flags) {
- final AndroidPackage pkg;
- final SharedUserSetting sus;
- synchronized (mPm.mLock) {
- pkg = mPm.mPackages.get(ps.getPackageName());
- sus = mPm.mSettings.getSharedUserSettingLPr(ps);
- }
-
- mAppDataHelper.destroyAppProfilesLIF(pkg);
-
- final List<AndroidPackage> sharedUserPkgs =
- sus != null ? sus.getPackages() : Collections.emptyList();
- final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
- final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
- : new int[] {userId};
- for (int nextUserId : userIds) {
- if (DEBUG_REMOVE) {
- Slog.d(TAG, "Updating package:" + ps.getPackageName() + " install state for user:"
- + nextUserId);
- }
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- mAppDataHelper.destroyAppDataLIF(pkg, nextUserId,
- FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
- ps.setCeDataInode(-1, nextUserId);
- }
- mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId());
- preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
- nextUserId);
- mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
- }
- mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
- sharedUserPkgs, userId);
-
- if (outInfo != null) {
- if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
- outInfo.mDataRemoved = true;
- }
- outInfo.mRemovedPackage = ps.getPackageName();
- outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
- outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
- outInfo.mRemovedAppId = ps.getAppId();
- outInfo.mRemovedUsers = userIds;
- outInfo.mBroadcastUsers = userIds;
- outInfo.mIsExternal = ps.isExternalStorage();
- outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
- }
- }
-
@GuardedBy("mPm.mInstallLock")
private void deleteInstalledPackageLIF(PackageSetting ps,
boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
@@ -607,7 +549,7 @@
}
@GuardedBy("mPm.mLock")
- private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user) {
+ private void markPackageUninstalledForUserLPw(PackageSetting ps, UserHandle user, int flags) {
final int[] userIds = (user == null || user.getIdentifier() == UserHandle.USER_ALL)
? mUserManagerInternal.getUserIds()
: new int[] {user.getIdentifier()};
@@ -616,6 +558,12 @@
Slog.d(TAG, "Marking package:" + ps.getPackageName()
+ " uninstalled for user:" + nextUserId);
}
+ // Preserve ArchiveState if this is not a full uninstall
+ ArchiveState archiveState =
+ (flags & DELETE_KEEP_DATA) == 0
+ ? null
+ : ps.getUserStateOrDefault(nextUserId).getArchiveState();
+
ps.setUserState(nextUserId,
ps.getCeDataInode(nextUserId),
COMPONENT_ENABLED_STATE_DEFAULT,
@@ -636,7 +584,7 @@
null /*splashScreenTheme*/,
0 /*firstInstallTime*/,
PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
- null /*archiveState*/);
+ archiveState);
}
mPm.mSettings.writeKernelMappingLPr(ps);
}
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index f3ea42e..2a00a44 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -619,7 +619,7 @@
pw.println(" --checkin: dump for a checkin");
pw.println(" -f: print details of intent filters");
pw.println(" -h: print this help");
- pw.println(" ---proto: dump data to proto");
+ pw.println(" --proto: dump data to proto");
pw.println(" --all-components: include all component names in package dump");
pw.println(" --include-apex: includes the apex packages in package dump");
pw.println(" cmd may be one of:");
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 76203ac..8f7b721 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -30,6 +30,7 @@
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstaller;
@@ -99,6 +100,9 @@
private final PackageInstallerService mInstallerService;
@NonNull
+ private final PackageArchiverService mPackageArchiverService;
+
+ @NonNull
private final PackageProperty mPackageProperty;
@NonNull
@@ -127,7 +131,8 @@
@Nullable ComponentName instantAppResolverSettingsComponent,
@NonNull String requiredSupplementalProcessPackage,
@Nullable String servicesExtensionPackageName,
- @Nullable String sharedSystemSharedLibraryPackageName) {
+ @Nullable String sharedSystemSharedLibraryPackageName,
+ @NonNull PackageArchiverService packageArchiverService) {
mService = service;
mContext = context;
mDexOptHelper = dexOptHelper;
@@ -143,6 +148,7 @@
mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
mServicesExtensionPackageName = servicesExtensionPackageName;
mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
+ mPackageArchiverService = packageArchiverService;
}
protected Computer snapshot() {
@@ -616,6 +622,12 @@
@Override
@Deprecated
+ public final IPackageArchiverService getPackageArchiverService() {
+ return mPackageArchiverService;
+ }
+
+ @Override
+ @Deprecated
public final void getPackageSizeInfo(final String packageName, int userId,
final IPackageStatsObserver observer) {
throw new UnsupportedOperationException(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 3e18387..dd04340 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1144,8 +1144,16 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
- parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
- AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+ if (request.getPackageLite() == null || !PackageInstallerSession.isArchivedInstallation(
+ request.getInstallFlags())) {
+ // TODO: pass packageLite from install request instead of reparsing the package
+ parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
+ AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+ } else {
+ // Archived install mode, no APK.
+ parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(),
+ parseFlags);
+ }
} catch (PackageManagerException e) {
throw new PrepareFailure("Failed parse during installPackageLI", e);
} finally {
@@ -1547,6 +1555,7 @@
// TODO: Are these system flags actually set properly at this stage?
boolean isUpdatedSystemAppInferred =
pkgSetting != null && pkgSetting.isSystem();
+ // derivePackageAbi works OK for archived packages despite logging some errors.
final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
systemApp, (isUpdatedSystemAppFromExistingSetting
@@ -1582,7 +1591,8 @@
final PackageFreezer freezer =
freezePackageForInstall(pkgName, UserHandle.USER_ALL, installFlags,
- "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED);
+ "installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
+
boolean shouldCloseFreezerBeforeReturn = true;
try {
final PackageState oldPackageState;
@@ -2032,11 +2042,11 @@
}
private PackageFreezer freezePackageForInstall(String packageName, int userId, int installFlags,
- String killReason, int exitInfoReason) {
+ String killReason, int exitInfoReason, InstallRequest request) {
if ((installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0) {
- return new PackageFreezer(mPm);
+ return new PackageFreezer(mPm, request);
} else {
- return mPm.freezePackage(packageName, userId, killReason, exitInfoReason);
+ return mPm.freezePackage(packageName, userId, killReason, exitInfoReason, request);
}
}
@@ -3207,7 +3217,7 @@
try (PackageFreezer freezer =
mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
"setEnabledSetting",
- ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+ ApplicationExitInfo.REASON_PACKAGE_UPDATED, null /* request */)) {
pkg = installStubPackageLI(stubPkg, parseFlags, 0 /*scanFlags*/);
mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
synchronized (mPm.mLock) {
@@ -3231,7 +3241,8 @@
try (PackageFreezer freezer =
mPm.freezePackage(stubPkg.getPackageName(), UserHandle.USER_ALL,
"setEnabledSetting",
- ApplicationExitInfo.REASON_PACKAGE_UPDATED)) {
+ ApplicationExitInfo.REASON_PACKAGE_UPDATED,
+ null /* request */)) {
synchronized (mPm.mLock) {
// NOTE: Ensure the system package is enabled; even for a compressed stub.
// If we don't, installing the system package fails during scan
@@ -4291,7 +4302,8 @@
+ " name: " + pkgSetting.getPackageName());
try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
parsedPackage.getPackageName(), UserHandle.USER_ALL,
- "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER)) {
+ "scanPackageInternalLI", ApplicationExitInfo.REASON_OTHER,
+ null /* request */)) {
DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
mPm.mUserManager.getUserIds(), 0, null, false);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 6fc14e8..fe7c086 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -35,6 +35,7 @@
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
+import android.content.pm.parsing.PackageLite;
import android.net.Uri;
import android.os.Build;
import android.os.Process;
@@ -93,6 +94,8 @@
private int[] mNewUsers;
@Nullable
private AndroidPackage mPkg;
+ @Nullable
+ private PackageLite mPackageLite;
private int mReturnCode;
private int mInternalErrorCode;
@Nullable
@@ -142,6 +145,7 @@
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
params.mDataLoaderType, params.mPackageSource,
params.mApplicationEnabledSettingPersistent);
+ mPackageLite = params.mPackageLite;
mPackageMetrics = new PackageMetrics(this);
mIsInstallInherit = params.mIsInherit;
mSessionId = params.mSessionId;
@@ -306,6 +310,11 @@
}
@Nullable
+ public PackageLite getPackageLite() {
+ return mPackageLite;
+ }
+
+ @Nullable
public String getTraceMethod() {
return mInstallArgs == null ? null : mInstallArgs.mTraceMethod;
}
@@ -831,4 +840,16 @@
}
}
}
+
+ public void onFreezeStarted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepStarted(PackageMetrics.STEP_FREEZE_INSTALL);
+ }
+ }
+
+ public void onFreezeCompleted() {
+ if (mPackageMetrics != null) {
+ mPackageMetrics.onStepFinished(PackageMetrics.STEP_FREEZE_INSTALL);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9ac983d..6233c9b 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -29,6 +29,7 @@
import android.os.CreateAppDataResult;
import android.os.IBinder;
import android.os.IInstalld;
+import android.os.ParcelFileDescriptor;
import android.os.ReconcileSdkDataArgs;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -1161,6 +1162,55 @@
}
}
+ /**
+ * Returns an auth token for the provided writable FD.
+ *
+ * @param authFd a file descriptor to proof that the caller can write to the file.
+ * @param appUid uid of the calling app.
+ * @param userId id of the user whose app file to enable fs-verity.
+ *
+ * @return authToken, or null if a remote call shouldn't be continued. See {@link
+ * #checkBeforeRemote}.
+ *
+ * @throws InstallerException if the remote call failed.
+ */
+ public IInstalld.IFsveritySetupAuthToken createFsveritySetupAuthToken(
+ ParcelFileDescriptor authFd, int appUid, @UserIdInt int userId)
+ throws InstallerException {
+ if (!checkBeforeRemote()) {
+ return null;
+ }
+ try {
+ return mInstalld.createFsveritySetupAuthToken(authFd, appUid, userId);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Enables fs-verity to the given app file.
+ *
+ * @param authToken a token previously returned from {@link #createFsveritySetupAuthToken}.
+ * @param filePath file path of the package to enable fs-verity.
+ * @param packageName name of the package.
+ *
+ * @return 0 if the operation was successful, otherwise {@code errno}.
+ *
+ * @throws InstallerException if the remote call failed (e.g. see {@link #checkBeforeRemote}).
+ */
+ public int enableFsverity(IInstalld.IFsveritySetupAuthToken authToken, String filePath,
+ String packageName) throws InstallerException {
+ if (!checkBeforeRemote()) {
+ throw new InstallerException("fs-verity wasn't enabled with an isolated installer");
+ }
+ BlockGuard.getVmPolicy().onPathAccess(filePath);
+ try {
+ return mInstalld.enableFsverity(authToken, filePath, packageName);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public static class InstallerException extends Exception {
public InstallerException(String detailMessage) {
super(detailMessage);
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 30a23bf..fe6a8a1 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -543,7 +543,6 @@
mInstallPackageHelper.installPackagesTraced(installRequests);
for (InstallRequest request : installRequests) {
- request.onInstallCompleted();
doPostInstall(request);
}
}
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 01c2734..148e0df 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -147,7 +147,8 @@
final PackageFreezer freezer;
synchronized (mPm.mLock) {
freezer = mPm.freezePackage(packageName, UserHandle.USER_ALL,
- "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED);
+ "movePackageInternal", ApplicationExitInfo.REASON_USER_REQUESTED,
+ null /* request */);
}
final Bundle extras = new Bundle();
diff --git a/services/core/java/com/android/server/pm/ArchiveManager.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
similarity index 62%
rename from services/core/java/com/android/server/pm/ArchiveManager.java
rename to services/core/java/com/android/server/pm/PackageArchiverService.java
index 5435206..9c31dc9 100644
--- a/services/core/java/com/android/server/pm/ArchiveManager.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -22,11 +22,13 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.IPackageArchiverService;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Binder;
+import android.os.ParcelableException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -47,7 +49,9 @@
* while the data directory is kept. Archived apps are included in the list of launcher apps where
* tapping them re-installs the full app.
*/
-final class ArchiveManager {
+public class PackageArchiverService extends IPackageArchiverService.Stub {
+
+ private static final String TAG = "PackageArchiver";
private final Context mContext;
private final PackageManagerService mPm;
@@ -55,36 +59,39 @@
@Nullable
private LauncherApps mLauncherApps;
- ArchiveManager(Context context, PackageManagerService mPm) {
+ public PackageArchiverService(Context context, PackageManagerService mPm) {
this.mContext = context;
this.mPm = mPm;
}
- void archiveApp(
+ @Override
+ public void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
- @NonNull UserHandle user,
- @NonNull IntentSender intentSender) throws PackageManager.NameNotFoundException {
+ @NonNull IntentSender intentSender,
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
- Objects.requireNonNull(user);
Objects.requireNonNull(intentSender);
+ Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
- int callingUid = Binder.getCallingUid();
- int userId = user.getIdentifier();
- String callingPackageName = snapshot.getNameForUid(callingUid);
- snapshot.enforceCrossUserPermission(callingUid, userId, true, true,
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+ int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+ snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
- verifyCaller(callerPackageName, callingPackageName);
- PackageStateInternal ps = getPackageState(packageName, snapshot, callingUid, user);
- verifyInstaller(packageName, ps.getInstallSource());
+ verifyCaller(providedUid, binderUid);
+ PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
+ verifyInstaller(packageName, ps);
+ // TODO(b/291569242) Verify that this list is not empty and return failure with
+ // intentsender
List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
ps.getPackageName(),
new UserHandle(userId));
- // TODO(b/291569242) Verify that this list is not empty and return failure with intentsender
+ // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
storeArchiveState(ps, mainActivities, userId);
// TODO(b/278553670) Add special strings for the delete dialog
@@ -93,15 +100,25 @@
callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
}
+ private static void verifyInstaller(String packageName, PackageStateInternal ps) {
+ if (ps.getInstallSource().mUpdateOwnerPackageName == null
+ && ps.getInstallSource().mInstallerPackageName == null) {
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("No installer found to archive app %s.",
+ packageName)));
+ }
+ }
+
@NonNull
private static PackageStateInternal getPackageState(String packageName,
- Computer snapshot, int callingUid, UserHandle user)
- throws PackageManager.NameNotFoundException {
+ Computer snapshot, int callingUid, int userId) {
PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
- user.getIdentifier());
+ userId);
if (ps == null) {
- throw new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName));
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName)));
}
return ps;
}
@@ -114,8 +131,7 @@
}
private void storeArchiveState(PackageStateInternal ps,
- List<LauncherActivityInfo> mainActivities, int userId)
- throws PackageManager.NameNotFoundException {
+ List<LauncherActivityInfo> mainActivities, int userId) {
List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (int i = 0; i < mainActivities.size(); i++) {
// TODO(b/278553670) Extract and store launcher icons
@@ -130,41 +146,34 @@
? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
synchronized (mPm.mLock) {
- getPackageSetting(ps.getPackageName(), userId).modifyUserState(userId).setArchiveState(
- new ArchiveState(activityInfos, installerPackageName));
+ PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+ packageSetting
+ .modifyUserState(userId)
+ .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
}
}
@NonNull
@GuardedBy("mPm.mLock")
- private PackageSetting getPackageSetting(String packageName, int userId)
- throws PackageManager.NameNotFoundException {
+ private PackageSetting getPackageSettingLocked(String packageName, int userId) {
PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ // Shouldn't happen, we already verify presence of the package in getPackageState()
if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
- throw new PackageManager.NameNotFoundException(
- TextUtils.formatSimple("Package %s not found.", packageName));
+ throw new ParcelableException(
+ new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple("Package %s not found.", packageName)));
}
return ps;
}
- private static void verifyCaller(String callerPackageName, String callingPackageName) {
- if (!TextUtils.equals(callingPackageName, callerPackageName)) {
+ private static void verifyCaller(int providedUid, int binderUid) {
+ if (providedUid != binderUid) {
throw new SecurityException(
TextUtils.formatSimple(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- callerPackageName,
- callingPackageName));
- }
- }
-
- private static void verifyInstaller(String packageName, InstallSource installSource) {
- // TODO(b/291060290) Verify installer supports unarchiving
- if (installSource.mUpdateOwnerPackageName == null
- && installSource.mInstallerPackageName == null) {
- throw new SecurityException(
- TextUtils.formatSimple("No installer found to archive app %s.",
- packageName));
+ "The UID %s of callerPackageName set by the caller doesn't match the "
+ + "caller's actual UID %s.",
+ providedUid,
+ binderUid));
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 841b66e..7c56157 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import dalvik.system.CloseGuard;
@@ -29,6 +30,8 @@
* app code/data to prevent the app from running while you're working.
*/
final class PackageFreezer implements AutoCloseable {
+ @Nullable private InstallRequest mInstallRequest;
+
private final String mPackageName;
private final AtomicBoolean mClosed = new AtomicBoolean();
@@ -43,18 +46,29 @@
* {@link PackageManager#INSTALL_DONT_KILL_APP} or
* {@link PackageManager#DELETE_DONT_KILL_APP}.
*/
- PackageFreezer(PackageManagerService pm) {
+
+ PackageFreezer(PackageManagerService pm, @Nullable InstallRequest request) {
mPm = pm;
mPackageName = null;
mClosed.set(true);
mCloseGuard.open("close");
+ mInstallRequest = request;
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeStarted();
+ }
}
PackageFreezer(String packageName, int userId, String killReason,
- PackageManagerService pm, int exitInfoReason) {
+ PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) {
mPm = pm;
mPackageName = packageName;
+ mInstallRequest = request;
final PackageSetting ps;
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeStarted();
+ }
synchronized (mPm.mLock) {
final int refCounts = mPm.mFrozenPackages
.getOrDefault(mPackageName, 0 /* defaultValue */) + 1;
@@ -92,5 +106,10 @@
}
}
}
+ // We only focus on the install Freeze metrics now
+ if (mInstallRequest != null) {
+ mInstallRequest.onFreezeCompleted();
+ mInstallRequest = null;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 83d2f6a..b4ca477 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -93,6 +93,7 @@
mPm.mRunningInstalls.delete(msg.arg1);
request.closeFreezer();
+ request.onInstallCompleted();
request.runPostInstallRunnable();
if (!request.isInstallExistingForUser()) {
mInstallPackageHelper.handlePackagePostInstall(request, didRestore);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 923bbab..1554072 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -31,6 +31,7 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -52,6 +53,7 @@
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
import static com.android.server.pm.PackageManagerServiceUtils.isInstalledByAdb;
+import static com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
import android.Manifest;
import android.annotation.AnyThread;
@@ -838,6 +840,10 @@
params.dataLoaderParams.getComponentName().getPackageName());
}
+ static boolean isArchivedInstallation(int installFlags) {
+ return (installFlags & PackageManager.INSTALL_ARCHIVED) != 0;
+ }
+
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
@@ -896,6 +902,10 @@
return isSystemDataLoaderInstallation(this.params);
}
+ private boolean isArchivedInstallation() {
+ return isArchivedInstallation(this.params.installFlags);
+ }
+
/**
* @return {@code true} iff the installing is app an device owner or affiliated profile owner.
*/
@@ -1146,6 +1156,17 @@
if (isIncrementalInstallation() && !IncrementalManager.isAllowed()) {
throw new IllegalArgumentException("Incremental installation not allowed.");
}
+
+ if (isArchivedInstallation()) {
+ if (params.mode != SessionParams.MODE_FULL_INSTALL) {
+ throw new IllegalArgumentException(
+ "Archived installation can only be full install.");
+ }
+ if (!isStreamingInstallation() || !isSystemDataLoaderInstallation()) {
+ throw new IllegalArgumentException(
+ "Archived installation can only use Streaming System DataLoader.");
+ }
+ }
}
/**
@@ -1462,6 +1483,58 @@
}
@GuardedBy("mLock")
+ private List<ApkLite> getAddedApkLitesLocked() throws PackageManagerException {
+ if (!isArchivedInstallation()) {
+ List<File> files = getAddedApksLocked();
+ final List<ApkLite> result = new ArrayList<>(files.size());
+
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ for (int i = 0, size = files.size(); i < size; ++i) {
+ final ParseResult<ApkLite> parseResult = ApkLiteParseUtils.parseApkLite(
+ input.reset(), files.get(i),
+ ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+ if (parseResult.isError()) {
+ throw new PackageManagerException(parseResult.getErrorCode(),
+ parseResult.getErrorMessage(), parseResult.getException());
+ }
+ result.add(parseResult.getResult());
+ }
+
+ return result;
+ }
+
+ InstallationFile[] files = getInstallationFilesLocked();
+ final List<ApkLite> result = new ArrayList<>(files.length);
+
+ for (int i = 0, size = files.length; i < size; ++i) {
+ File file = new File(stageDir, files[i].getName());
+ if (!sAddedApkFilter.accept(file)) {
+ continue;
+ }
+
+ final Metadata metadata;
+ try {
+ metadata = Metadata.fromByteArray(files[i].getMetadata());
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to ", e);
+ }
+ if (metadata.getMode() != Metadata.ARCHIVED) {
+ throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "File metadata is not for ARCHIVED package: " + file);
+ }
+
+ var archPkg = metadata.getArchivedPackage();
+ if (archPkg.packageName == null || archPkg.signingDetails == null) {
+ throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "ArchivedPackage does not contain required info: " + file);
+ }
+ result.add(new ApkLite(file.getAbsolutePath(), archPkg));
+ }
+ return result;
+ }
+
+ @GuardedBy("mLock")
private List<File> getRemovedFilesLocked() {
String[] names = getNamesLocked();
return filterFiles(stageDir, names, sRemovedFilter);
@@ -2732,9 +2805,18 @@
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session not sealed");
}
- Objects.requireNonNull(mPackageName);
- Objects.requireNonNull(mSigningDetails);
- Objects.requireNonNull(mResolvedBaseFile);
+ if (mPackageName == null) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session no package name");
+ }
+ if (mSigningDetails == null) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session no signing data");
+ }
+ if (mResolvedBaseFile == null) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Session no resolved base file");
+ }
final PackageLite result;
if (!isApexSession()) {
// For mode inherit existing, it would link/copy existing files to stage dir in
@@ -3176,7 +3258,7 @@
}
}
- final List<File> addedFiles = getAddedApksLocked();
+ final List<ApkLite> addedFiles = getAddedApkLitesLocked();
if (addedFiles.isEmpty()
&& (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
@@ -3190,15 +3272,7 @@
final ArraySet<String> requiredSplitTypes = new ArraySet<>();
final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>();
final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- for (File addedFile : addedFiles) {
- final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
- addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
- if (result.isError()) {
- throw new PackageManagerException(result.getErrorCode(),
- result.getErrorMessage(), result.getException());
- }
-
- final ApkLite apk = result.getResult();
+ for (ApkLite apk : addedFiles) {
if (!stagedSplits.add(apk.getSplitName())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Split " + apk.getSplitName() + " was defined multiple times");
@@ -3214,7 +3288,7 @@
}
mHasDeviceAdminReceiver = apk.isHasDeviceAdminReceiver();
- assertApkConsistentLocked(String.valueOf(addedFile), apk);
+ assertApkConsistentLocked(String.valueOf(apk), apk);
// Take this opportunity to enforce uniform naming
final String targetName = ApkLiteParseUtils.splitNameToFileName(apk);
@@ -3235,7 +3309,10 @@
}
final File targetFile = new File(stageDir, targetName);
- resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());
+ if (!isArchivedInstallation()) {
+ final File sourceFile = new File(apk.getPath());
+ resolveAndStageFileLocked(sourceFile, targetFile, apk.getSplitName());
+ }
// Base is coming from session
if (apk.getSplitName() == null) {
@@ -4005,6 +4082,11 @@
NativeLibraryHelper.removeNativeBinariesFromDirLI(libDir, true);
}
+ // Skip native libraries processing for archival installation.
+ if (isArchivedInstallation()) {
+ return;
+ }
+
NativeLibraryHelper.Handle handle = null;
try {
handle = NativeLibraryHelper.Handle.create(packageLite);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 7ccf713..8faadba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -77,6 +77,7 @@
import android.content.IntentSender.SendIntentException;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.AuxiliaryResolveInfo;
import android.content.pm.ChangedPackages;
import android.content.pm.Checksum;
@@ -115,7 +116,11 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.overlay.OverlayPaths;
+import android.content.pm.parsing.ApkLite;
+import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -796,6 +801,8 @@
final PackageInstallerService mInstallerService;
+ final PackageArchiverService mArchiverService;
+
final ArtManagerService mArtManagerService;
// TODO(b/260124949): Remove these.
@@ -1624,7 +1631,8 @@
(i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
context),
- (i, pm) -> new UpdateOwnershipHelper());
+ (i, pm) -> new UpdateOwnershipHelper(),
+ (i, pm) -> new PackageArchiverService(i.getContext(), pm));
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1769,6 +1777,7 @@
mFactoryTest = testParams.factoryTest;
mIncrementalManager = testParams.incrementalManager;
mInstallerService = testParams.installerService;
+ mArchiverService = testParams.archiverService;
mInstantAppRegistry = testParams.instantAppRegistry;
mChangedPackagesTracker = testParams.changedPackagesTracker;
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
@@ -1967,9 +1976,6 @@
mApexManager = injector.getApexManager();
mAppsFilter = mInjector.getAppsFilter();
- mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
- mInjector.getUserManagerInternal(), new DeletePackageHelper(this));
-
mChangedPackagesTracker = new ChangedPackagesTracker();
mAppInstallDir = new File(Environment.getDataDirectory(), "app");
@@ -1983,8 +1989,11 @@
mAppDataHelper = new AppDataHelper(this);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
- mAppDataHelper);
+ mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper);
+
+ mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
+ mInjector.getUserManagerInternal(), mDeletePackageHelper);
+
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
mPreferredActivityHelper = new PreferredActivityHelper(this);
mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
@@ -2348,6 +2357,7 @@
});
mInstallerService = mInjector.getPackageInstallerService();
+ mArchiverService = mInjector.getPackageArchiverService();
final ComponentName instantAppResolverComponent = getInstantAppResolver(computer);
if (instantAppResolverComponent != null) {
if (DEBUG_INSTANT) {
@@ -4289,16 +4299,17 @@
}
public PackageFreezer freezePackage(String packageName, int userId, String killReason,
- int exitInfoReason) {
- return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason);
+ int exitInfoReason, InstallRequest request) {
+ return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request);
}
public PackageFreezer freezePackageForDelete(String packageName, int userId, int deleteFlags,
String killReason, int exitInfoReason) {
if ((deleteFlags & PackageManager.DELETE_DONT_KILL_APP) != 0) {
- return new PackageFreezer(this);
+ return new PackageFreezer(this, null /* request */);
} else {
- return freezePackage(packageName, userId, killReason, exitInfoReason);
+ return freezePackage(packageName, userId, killReason, exitInfoReason,
+ null /* request */);
}
}
@@ -4608,7 +4619,7 @@
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
- mSharedSystemSharedLibraryPackageName);
+ mSharedSystemSharedLibraryPackageName, mArchiverService);
}
@Override
@@ -4627,7 +4638,7 @@
try (PackageFreezer ignored =
freezePackage(packageName, UserHandle.USER_ALL,
"clearApplicationProfileData",
- ApplicationExitInfo.REASON_OTHER)) {
+ ApplicationExitInfo.REASON_OTHER, null /* request */)) {
synchronized (mInstallLock) {
mAppDataHelper.clearAppProfilesLIF(pkg);
}
@@ -4670,7 +4681,7 @@
final boolean succeeded;
try (PackageFreezer freezer = freezePackage(packageName, UserHandle.USER_ALL,
"clearApplicationUserData",
- ApplicationExitInfo.REASON_USER_REQUESTED)) {
+ ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */)) {
synchronized (mInstallLock) {
succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
userId);
@@ -6292,6 +6303,38 @@
}
}
+ @Override
+ public ArchivedPackageParcel getArchivedPackage(String apkPath) {
+ ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
+ new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
+ if (result.isError()) {
+ throw new IllegalArgumentException(result.getErrorMessage(), result.getException());
+ }
+ final ApkLite apk = result.getResult();
+
+ ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
+ archPkg.packageName = apk.getPackageName();
+ archPkg.signingDetails = apk.getSigningDetails();
+
+ archPkg.versionCodeMajor = apk.getVersionCodeMajor();
+ archPkg.versionCode = apk.getVersionCode();
+
+ archPkg.targetSdkVersion = apk.getTargetSdkVersion();
+
+ // These get translated in flags important for user data management.
+ archPkg.clearUserDataAllowed = apk.isClearUserDataAllowed();
+ archPkg.backupAllowed = apk.isBackupAllowed();
+ archPkg.defaultToDeviceProtectedStorage =
+ apk.isDefaultToDeviceProtectedStorage();
+ archPkg.requestLegacyExternalStorage = apk.isRequestLegacyExternalStorage();
+ archPkg.userDataFragile = apk.isUserDataFragile();
+ archPkg.clearUserDataOnFailedRestoreAllowed =
+ apk.isClearUserDataOnFailedRestoreAllowed();
+
+ return archPkg;
+ }
+
/**
* Wait for the handler to finish handling all pending messages.
* @param timeoutMillis Maximum time in milliseconds to wait.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 51840e7..9495279 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -127,6 +127,8 @@
mPreparingPackageParserProducer;
private final Singleton<PackageInstallerService>
mPackageInstallerServiceProducer;
+ private final Singleton<PackageArchiverService>
+ mPackageArchiverServiceProducer;
private final ProducerWithArgument<InstantAppResolverConnection, ComponentName>
mInstantAppResolverConnectionProducer;
private final Singleton<LegacyPermissionManagerInternal>
@@ -185,7 +187,8 @@
Producer<IBackupManager> iBackupManager,
Producer<SharedLibrariesImpl> sharedLibrariesProducer,
Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
- Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
+ Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
+ Producer<PackageArchiverService> packageArchiverServiceProducer) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -241,6 +244,7 @@
mCrossProfileIntentFilterHelperProducer = new Singleton<>(
crossProfileIntentFilterHelperProducer);
mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
+ mPackageArchiverServiceProducer = new Singleton<>(packageArchiverServiceProducer);
}
/**
@@ -387,6 +391,10 @@
return mPackageInstallerServiceProducer.get(this, mPackageManager);
}
+ public PackageArchiverService getPackageArchiverService() {
+ return mPackageArchiverServiceProducer.get(this, mPackageManager);
+ }
+
public InstantAppResolverConnection getInstantAppResolverConnection(
ComponentName instantAppResolverComponent) {
return mInstantAppResolverConnectionProducer.produce(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index ca57209..b91ce4b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -60,6 +60,7 @@
public @Nullable String incidentReportApproverPackage;
public IncrementalManager incrementalManager;
public PackageInstallerService installerService;
+ public PackageArchiverService archiverService;
public InstantAppRegistry instantAppRegistry;
public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
public InstantAppResolverConnection instantAppResolverConnection;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index db997d8..2028231 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -919,16 +919,22 @@
final File packageFile = new File(packagePath);
final long sizeBytes;
- try {
- sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
- } catch (IOException e) {
- if (!packageFile.exists()) {
- ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
- } else {
- ret.recommendedInstallLocation = InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
- }
+ if (!PackageInstallerSession.isArchivedInstallation(flags)) {
+ try {
+ sizeBytes = InstallLocationUtils.calculateInstalledSize(pkg, abiOverride);
+ } catch (IOException e) {
+ if (!packageFile.exists()) {
+ ret.recommendedInstallLocation =
+ InstallLocationUtils.RECOMMEND_FAILED_INVALID_URI;
+ } else {
+ ret.recommendedInstallLocation =
+ InstallLocationUtils.RECOMMEND_FAILED_INVALID_APK;
+ }
- return ret;
+ return ret;
+ }
+ } else {
+ sizeBytes = 0;
}
final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8bdbe04..d9f1df5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -43,6 +43,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstaller;
@@ -82,6 +83,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.IUserManager;
import android.os.ParcelFileDescriptor;
@@ -105,6 +107,7 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.IntArray;
+import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -246,6 +249,8 @@
return runStreamingInstall();
case "install-incremental":
return runIncrementalInstall();
+ case "install-archived":
+ return runArchivedInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -1549,6 +1554,16 @@
return doRunInstall(params);
}
+ private int runArchivedInstall() throws RemoteException {
+ final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
+ params.sessionParams.installFlags |= PackageManager.INSTALL_ARCHIVED;
+ if (params.sessionParams.dataLoaderParams == null) {
+ params.sessionParams.setDataLoaderParams(
+ PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
+ }
+ return doRunInstall(params);
+ }
+
private int runIncrementalInstall() throws RemoteException {
final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
if (params.sessionParams.dataLoaderParams == null) {
@@ -1565,9 +1580,22 @@
private int doRunInstall(final InstallParams params) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
+ int requestUserId = params.userId;
+ if (requestUserId != UserHandle.USER_ALL && requestUserId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(requestUserId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + requestUserId + " doesn't exist]");
+ return 1;
+ }
+ }
+
final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
final boolean isApex =
(params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
+ final boolean installArchived =
+ (params.sessionParams.installFlags & PackageManager.INSTALL_ARCHIVED) != 0;
ArrayList<String> args = getRemainingArgs();
@@ -1584,6 +1612,13 @@
return 1;
}
+ if (installArchived) {
+ if (hasSplits) {
+ pw.println("Error: can't have SPLIT(s) for Archival install");
+ return 1;
+ }
+ }
+
if (!isStreaming) {
if (fromStdIn && hasSplits) {
pw.println("Error: can't specify SPLIT(s) along with STDIN");
@@ -1602,8 +1637,8 @@
boolean abandonSession = true;
try {
if (isStreaming) {
- if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex)
- != PackageInstaller.STATUS_SUCCESS) {
+ if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes, isApex,
+ installArchived) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
} else {
@@ -2319,6 +2354,15 @@
break;
case "--user":
userId = UserHandle.parseUserArg(getNextArgRequired());
+ if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+ UserManagerInternal umi =
+ LocalServices.getService(UserManagerInternal.class);
+ UserInfo userInfo = umi.getUserInfo(userId);
+ if (userInfo == null) {
+ pw.println("Failure [user " + userId + " doesn't exist]");
+ return 1;
+ }
+ }
break;
case "--versionCode":
versionCode = Long.parseLong(getNextArgRequired());
@@ -3836,7 +3880,7 @@
}
private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
- boolean isApex) throws RemoteException {
+ boolean isApex, boolean installArchived) throws RemoteException {
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
@@ -3845,9 +3889,17 @@
// 1. Single file from stdin.
if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
final String name = "base" + RANDOM.nextInt() + "." + (isApex ? "apex" : "apk");
- final Metadata metadata = Metadata.forStdIn(name);
- session.addFile(LOCATION_DATA_APP, name, sessionSizeBytes,
- metadata.toByteArray(), null);
+ final long size;
+ final Metadata metadata;
+ if (!installArchived) {
+ metadata = Metadata.forStdIn(name);
+ size = sessionSizeBytes;
+ } else {
+ metadata = Metadata.forArchived(
+ getArchivedPackage(STDIN_PATH, sessionSizeBytes));
+ size = -1;
+ }
+ session.addFile(LOCATION_DATA_APP, name, size, metadata.toByteArray(), null);
return 0;
}
@@ -3856,16 +3908,21 @@
if (delimLocation != -1) {
// 2. File with specified size read from stdin.
+ if (installArchived) {
+ getOutPrintWriter().println(
+ "Error: can't install with size from STDIN for Archival install");
+ return 1;
+ }
if (processArgForStdin(arg, session) != 0) {
return 1;
}
} else {
// 3. Local file.
- processArgForLocalFile(arg, session);
+ processArgForLocalFile(arg, session, installArchived);
}
}
return 0;
- } catch (IllegalArgumentException e) {
+ } catch (IOException | IllegalArgumentException e) {
getErrPrintWriter().println("Failed to add file(s), reason: " + e);
getOutPrintWriter().println("Failure [failed to add file(s)]");
return 1;
@@ -3954,26 +4011,67 @@
}
}
- private void processArgForLocalFile(String arg, PackageInstaller.Session session) {
+ private ArchivedPackageParcel getArchivedPackage(String inPath, long sizeBytes)
+ throws RemoteException, IOException {
+ final var fdWithSize = openInFile(inPath, sizeBytes);
+ if (fdWithSize.first == null) {
+ throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+ }
+
+ File tmpFile = null;
+ final ParcelFileDescriptor fd = fdWithSize.first;
+ try (InputStream inStream = new AutoCloseInputStream(fd)) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ File tmpStagingDir = Environment.getDataAppDirectory(null);
+ tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp");
+
+ try (OutputStream outStream = new FileOutputStream(tmpFile)) {
+ Streams.copy(inStream, outStream);
+ }
+
+ return mInterface.getArchivedPackage(tmpFile.getAbsolutePath());
+ } finally {
+ if (tmpFile != null) {
+ tmpFile.delete();
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e);
+ }
+ }
+
+ private void processArgForLocalFile(String arg, PackageInstaller.Session session,
+ boolean installArchived) throws IOException, RemoteException {
final String inPath = arg;
final File file = new File(inPath);
final String name = file.getName();
- final long size = getFileStatSize(file);
- final Metadata metadata = Metadata.forLocalFile(inPath);
+ final long size;
+ final Metadata metadata;
+ if (installArchived) {
+ metadata = Metadata.forArchived(getArchivedPackage(inPath, -1));
+ size = 0;
+ } else {
+ metadata = Metadata.forLocalFile(inPath);
+ size = getFileStatSize(file);
+ }
byte[] v4signatureBytes = null;
- // Try to load the v4 signature file for the APK; it might not exist.
- final String v4SignaturePath = inPath + V4Signature.EXT;
- final ParcelFileDescriptor pfd = openFileForSystem(v4SignaturePath, "r");
- if (pfd != null) {
- try {
- final V4Signature v4signature = V4Signature.readFrom(pfd);
- v4signatureBytes = v4signature.toByteArray();
- } catch (IOException ex) {
- Slog.e(TAG, "V4 signature file exists but failed to be parsed.", ex);
- } finally {
- IoUtils.closeQuietly(pfd);
+ if (!installArchived) {
+ // Try to load the v4 signature file for the APK; it might not exist.
+ final String v4SignaturePath = inPath + V4Signature.EXT;
+ final ParcelFileDescriptor pfd = openFileForSystem(v4SignaturePath, "r");
+ if (pfd != null) {
+ try {
+ final V4Signature v4signature = V4Signature.readFrom(pfd);
+ v4signatureBytes = v4signature.toByteArray();
+ } catch (IOException ex) {
+ Slog.e(TAG, "V4 signature file exists but failed to be parsed.", ex);
+ } finally {
+ IoUtils.closeQuietly(pfd);
+ }
}
}
@@ -3995,6 +4093,32 @@
return 0;
}
+ private Pair<ParcelFileDescriptor, Long> openInFile(String inPath, long sizeBytes)
+ throws IOException {
+ final ParcelFileDescriptor fd;
+ if (STDIN_PATH.equals(inPath)) {
+ fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+ } else if (inPath != null) {
+ fd = openFileForSystem(inPath, "r");
+ if (fd == null) {
+ return Pair.create(null, -1L);
+ }
+ sizeBytes = fd.getStatSize();
+ if (sizeBytes < 0) {
+ fd.close();
+ getErrPrintWriter().println("Unable to get size of: " + inPath);
+ return Pair.create(null, -1L);
+ }
+ } else {
+ fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+ }
+ if (sizeBytes <= 0) {
+ getErrPrintWriter().println("Error: must specify an APK size");
+ return Pair.create(null, 1L);
+ }
+ return Pair.create(fd, sizeBytes);
+ }
+
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
@@ -4004,26 +4128,13 @@
final PrintWriter pw = getOutPrintWriter();
- final ParcelFileDescriptor fd;
- if (STDIN_PATH.equals(inPath)) {
- fd = ParcelFileDescriptor.dup(getInFileDescriptor());
- } else if (inPath != null) {
- fd = openFileForSystem(inPath, "r");
- if (fd == null) {
- return -1;
- }
- sizeBytes = fd.getStatSize();
- if (sizeBytes < 0) {
- getErrPrintWriter().println("Unable to get size of: " + inPath);
- return -1;
- }
- } else {
- fd = ParcelFileDescriptor.dup(getInFileDescriptor());
+ final var fdWithSize = openInFile(inPath, sizeBytes);
+ if (fdWithSize.first == null) {
+ long resultCode = fdWithSize.second;
+ return (int) resultCode;
}
- if (sizeBytes <= 0) {
- getErrPrintWriter().println("Error: must specify an APK size");
- return 1;
- }
+ final ParcelFileDescriptor fd = fdWithSize.first;
+ sizeBytes = fdWithSize.second;
session.write(splitName, 0, sizeBytes, fd);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index a1e5153..fbe5a51 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -18,9 +18,11 @@
import android.annotation.NonNull;
import android.content.ComponentName;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInstaller;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
import android.service.dataloader.DataLoaderService;
@@ -37,6 +39,7 @@
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
+import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;
@@ -136,9 +139,13 @@
* Everything streamed.
*/
static final byte STREAMING = 3;
+ /**
+ * Archived install.
+ */
+ static final byte ARCHIVED = 4;
private final byte mMode;
- private final String mData;
+ private final byte[] mData;
private final String mSalt;
private static AtomicLong sGlobalSalt = new AtomicLong((new SecureRandom()).nextLong());
@@ -156,6 +163,21 @@
return new Metadata(LOCAL_FILE, filePath, nextGlobalSalt().toString());
}
+ /** @hide */
+ @VisibleForTesting
+ public static Metadata forArchived(ArchivedPackageParcel archivedPackage) {
+ Parcel parcel = Parcel.obtain();
+ byte[] bytes;
+ try {
+ parcel.writeParcelable(archivedPackage, 0);
+ bytes = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+
+ return new Metadata(ARCHIVED, bytes, null);
+ }
+
static Metadata forDataOnlyStreaming(String fileId) {
return new Metadata(DATA_ONLY_STREAMING, fileId);
}
@@ -169,8 +191,12 @@
}
private Metadata(byte mode, String data, String salt) {
+ this(mode, (data != null ? data : "").getBytes(StandardCharsets.UTF_8), salt);
+ }
+
+ private Metadata(byte mode, byte[] data, String salt) {
this.mMode = mode;
- this.mData = (data == null) ? "" : data;
+ this.mData = data;
this.mSalt = salt;
}
@@ -181,22 +207,21 @@
int offset = 0;
final byte mode = bytes[offset];
offset += 1;
- final String data;
+ final byte[] data;
final String salt;
switch (mode) {
case LOCAL_FILE: {
int dataSize = ByteBuffer.wrap(bytes, offset, 4).order(
ByteOrder.LITTLE_ENDIAN).getInt();
offset += 4;
- data = new String(bytes, offset, dataSize, StandardCharsets.UTF_8);
+ data = Arrays.copyOfRange(bytes, offset, offset + dataSize);
offset += dataSize;
salt = new String(bytes, offset, bytes.length - offset,
StandardCharsets.UTF_8);
break;
}
default:
- data = new String(bytes, offset, bytes.length - offset,
- StandardCharsets.UTF_8);
+ data = Arrays.copyOfRange(bytes, offset, bytes.length);
salt = null;
break;
}
@@ -207,7 +232,7 @@
@VisibleForTesting
public byte[] toByteArray() {
final byte[] result;
- final byte[] dataBytes = this.mData.getBytes(StandardCharsets.UTF_8);
+ final byte[] dataBytes = this.mData;
switch (this.mMode) {
case LOCAL_FILE: {
int dataSize = dataBytes.length;
@@ -237,9 +262,26 @@
return this.mMode;
}
- String getData() {
+ byte[] getData() {
return this.mData;
}
+
+ ArchivedPackageParcel getArchivedPackage() {
+ if (getMode() != ARCHIVED) {
+ throw new IllegalStateException("Not an archived package metadata.");
+ }
+
+ Parcel parcel = Parcel.obtain();
+ ArchivedPackageParcel result;
+ try {
+ parcel.unmarshall(this.mData, 0, this.mData.length);
+ parcel.setDataPosition(0);
+ result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+ return result;
+ }
}
private static class DataLoader implements DataLoaderService.DataLoader {
@@ -278,7 +320,9 @@
case Metadata.LOCAL_FILE: {
ParcelFileDescriptor incomingFd = null;
try {
- incomingFd = getLocalFilePFD(shellCommand, metadata.getData());
+ final String filePath = new String(metadata.getData(),
+ StandardCharsets.UTF_8);
+ incomingFd = getLocalFilePFD(shellCommand, filePath);
mConnector.writeData(file.getName(), 0, incomingFd.getStatSize(),
incomingFd);
} finally {
@@ -286,6 +330,10 @@
}
break;
}
+ case Metadata.ARCHIVED: {
+ // Do nothing, metadata already contains everything needed for install.
+ break;
+ }
default:
Slog.e(TAG, "Unsupported metadata mode: " + metadata.getMode());
return false;
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 2d58fe5..c1580c4b 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -51,13 +51,15 @@
public static final int STEP_RECONCILE = 3;
public static final int STEP_COMMIT = 4;
public static final int STEP_DEXOPT = 5;
+ public static final int STEP_FREEZE_INSTALL = 6;
@IntDef(prefix = {"STEP_"}, value = {
STEP_PREPARE,
STEP_SCAN,
STEP_RECONCILE,
STEP_COMMIT,
- STEP_DEXOPT
+ STEP_DEXOPT,
+ STEP_FREEZE_INSTALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface StepInt {
@@ -109,10 +111,17 @@
long versionCode = 0, apksSize = 0;
if (success) {
- final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
- if (ps != null) {
- versionCode = ps.getVersionCode();
- apksSize = getApksSize(ps.getPath());
+ // TODO: Remove temp try-catch to avoid IllegalStateException. The reason is because
+ // the scan result is null for installExistingPackageAsUser(). Because it's installing
+ // a package that's already existing, there's no scanning or parsing involved
+ try {
+ final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+ if (ps != null) {
+ versionCode = ps.getVersionCode();
+ apksSize = getApksSize(ps.getPath());
+ }
+ } catch (IllegalStateException e) {
+ // no-op
}
}
diff --git a/services/core/java/com/android/server/pm/PrepareFailure.java b/services/core/java/com/android/server/pm/PrepareFailure.java
index 3180bac..09cb6b9 100644
--- a/services/core/java/com/android/server/pm/PrepareFailure.java
+++ b/services/core/java/com/android/server/pm/PrepareFailure.java
@@ -42,7 +42,8 @@
}
PrepareFailure(String message, Exception e) {
- super(((PackageManagerException) e).error,
+ super(e instanceof PackageManagerException ? ((PackageManagerException) e).error
+ : PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
ExceptionUtils.getCompleteMessage(message, e));
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 6d3b26c..d4f30fe 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -253,6 +253,56 @@
}
}
+ public void clearPackageStateForUserLIF(PackageSetting ps, int userId,
+ PackageRemovedInfo outInfo, int flags) {
+ final AndroidPackage pkg;
+ final SharedUserSetting sus;
+ synchronized (mPm.mLock) {
+ pkg = mPm.mPackages.get(ps.getPackageName());
+ sus = mPm.mSettings.getSharedUserSettingLPr(ps);
+ }
+
+ mAppDataHelper.destroyAppProfilesLIF(pkg);
+
+ final List<AndroidPackage> sharedUserPkgs =
+ sus != null ? sus.getPackages() : Collections.emptyList();
+ final PreferredActivityHelper preferredActivityHelper = new PreferredActivityHelper(mPm);
+ final int[] userIds = (userId == UserHandle.USER_ALL) ? mUserManagerInternal.getUserIds()
+ : new int[] {userId};
+ for (int nextUserId : userIds) {
+ if (DEBUG_REMOVE) {
+ Slog.d(TAG, "Updating package:" + ps.getPackageName() + " install state for user:"
+ + nextUserId);
+ }
+ if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ mAppDataHelper.destroyAppDataLIF(pkg, nextUserId,
+ FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
+ ps.setCeDataInode(-1, nextUserId);
+ }
+ mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId());
+ preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(),
+ nextUserId);
+ mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
+ }
+ mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
+ sharedUserPkgs, userId);
+
+ if (outInfo != null) {
+ if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
+ outInfo.mDataRemoved = true;
+ }
+ outInfo.mRemovedPackage = ps.getPackageName();
+ outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
+ outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
+ outInfo.mRemovedAppId = ps.getAppId();
+ outInfo.mRemovedUsers = userIds;
+ outInfo.mBroadcastUsers = userIds;
+ outInfo.mIsExternal = ps.isExternalStorage();
+ outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
+ }
+ }
+
+ // Called to clean up disabled system packages
public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles,
PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
synchronized (mPm.mInstallLock) {
@@ -314,7 +364,6 @@
int removedAppId = -1;
// writer
- boolean installedStateChanged = false;
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mPm.mLock) {
@@ -354,9 +403,10 @@
mPm.postPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
}
}
- // make sure to preserve per-user disabled state if this removal was just
+ // make sure to preserve per-user installed state if this removal was just
// a downgrade of a system app to the factory package
- if (outInfo != null && outInfo.mOrigUsers != null) {
+ boolean installedStateChanged = false;
+ if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) {
if (DEBUG_REMOVE) {
Slog.d(TAG, "Propagating install state across downgrade");
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 307867c..f04f338 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2916,11 +2916,7 @@
StringBuilder sb = new StringBuilder();
for (final PackageSetting ps : mPackages.values()) {
- // TODO(b/135203078): This doesn't handle multiple users
- final String dataPath = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM)
- .getAbsolutePath();
-
- if (ps.getPkg() == null || dataPath == null) {
+ if (ps.getPkg() == null) {
if (!"android".equals(ps.getPackageName())) {
Slog.w(TAG, "Skipping " + ps + " due to missing metadata");
}
@@ -2932,6 +2928,10 @@
continue;
}
+ // TODO(b/135203078): This doesn't handle multiple users
+ final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM);
+ final String dataPath = dataDir == null ? "null" : dataDir.getAbsolutePath();
+
final boolean isDebug = ps.getPkg().isDebuggable();
final IntArray gids = new IntArray();
for (final int userId : userIds) {
@@ -2973,7 +2973,7 @@
sb.append(ps.getSeInfo());
sb.append(" ");
final int gidsSize = gids.size();
- if (gids != null && gids.size() > 0) {
+ if (gids.size() > 0) {
sb.append(gids.get(0));
for (int i = 1; i < gidsSize; i++) {
sb.append(",");
@@ -4893,8 +4893,6 @@
pw.print("]");
}
pw.println();
- File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
- pw.print(prefix); pw.print(" dataDir="); pw.println(dataDir.getAbsolutePath());
if (pkg != null) {
pw.print(prefix); pw.print(" versionName="); pw.println(pkg.getVersionName());
pw.print(prefix); pw.print(" usesNonSdkApi="); pw.println(pkg.isNonSdkApiRequested());
@@ -5195,6 +5193,10 @@
pw.print(" installReason=");
pw.println(userState.getInstallReason());
+ final File dataDir = PackageInfoUtils.getDataDir(ps, user.id);
+ pw.print(" dataDir=");
+ pw.println(dataDir == null ? "null" : dataDir.getAbsolutePath());
+
final PackageUserStateInternal pus = ps.readUserState(user.id);
pw.print(" firstInstallTime=");
date.setTime(pus.getFirstInstallTimeMillis());
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index dd434fbe..3e4dd16 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3611,8 +3611,8 @@
// Otherwise check persisted shortcuts
getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> {
- cb.complete(getShortcutIconUriInternal(launcherUserId, launcherPackage,
- packageName, si, userId));
+ cb.complete(si == null ? null : getShortcutIconUriInternal(launcherUserId,
+ launcherPackage, packageName, si, userId));
});
}
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index d32eb22..e9c6aab 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -363,14 +363,14 @@
private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi,
boolean forBackup) throws IOException, XmlPullParserException {
- spi.waitForBitmapSaves();
if (forBackup) {
if (spi.getPackageUserId() != spi.getOwnerUserId()) {
return; // Don't save cross-user information.
}
+ spi.waitForBitmapSaves();
spi.saveToXml(out, forBackup);
} else {
- spi.saveShortcutPackageItem();
+ spi.scheduleSave();
}
}
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 6f0fe63..db5b9b1 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -155,7 +155,8 @@
for (PackageStateInternal ps : packages) {
freezers.add(mPm.freezePackage(ps.getPackageName(), UserHandle.USER_ALL,
- "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER));
+ "loadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER,
+ null /* request */));
synchronized (mPm.mInstallLock) {
final AndroidPackage pkg;
try {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b7f9aaf..85b60a0 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -51,6 +51,7 @@
import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
@@ -284,9 +285,9 @@
.setBadgeLabels(
com.android.internal.R.string.private_profile_label_badge)
.setBadgeColors(
- com.android.internal.R.color.system_accent1_900)
+ R.color.black)
.setDarkThemeBadgeColors(
- com.android.internal.R.color.system_accent1_900)
+ R.color.white)
.setDefaultRestrictions(getDefaultProfileRestrictions())
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 3a1fd7c..8c73ce8 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -191,7 +191,7 @@
// Perform package verification and enable rollback (unless we are simply moving the
// package).
if (!mOriginInfo.mExisting) {
- if (!isApex()) {
+ if (!isApex() && !isArchivedInstallation()) {
// TODO(b/182426975): treat APEX as APK when APK verification is concerned
sendApkVerificationRequest(pkgLite);
}
@@ -896,6 +896,9 @@
public boolean isApex() {
return (mInstallFlags & PackageManager.INSTALL_APEX) != 0;
}
+ public boolean isArchivedInstallation() {
+ return (mInstallFlags & PackageManager.INSTALL_ARCHIVED) != 0;
+ }
public boolean isStaged() {
return mIsStaged;
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 31856f1..f4f03f4 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -544,11 +544,6 @@
*/
public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
try {
- final String packageName = pkg.getPackageName();
- final String apkPath = pkg.getSplits().get(0).getPath();
- // TODO(b/143971007): Use a cross-user directory
- File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
- final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
|| pkg.isDefaultToDeviceProtectedStorage()) {
// Privileged apps prefer to load trusted code so they don't use compiled views.
@@ -558,6 +553,14 @@
// selinux permissions required for writing to user_de.
return false;
}
+ final String packageName = pkg.getPackageName();
+ final String apkPath = pkg.getSplits().get(0).getPath();
+ final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+ if (dataDir == null) {
+ // The app is not installed on the target user and doesn't have a data dir
+ return false;
+ }
+ final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
") to " + outDexFile);
final long callingId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
index 6405ea5..c5b65a3 100644
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
@@ -40,8 +40,11 @@
public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
try {
final String packageName = ps.getPackageName();
- // TODO(b/143971007): Use a cross-user directory
- File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+ final File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+ if (dataDir == null) {
+ // The app is not installed on the target user and doesn't have a data dir
+ return false;
+ }
final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
") to " + outDexFile);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 27812df..fc07909 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -443,7 +443,7 @@
updateApplicationInfo(info, flags, state);
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
// TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
PackageStateUnserialized pkgState = pkgSetting.getTransientState();
@@ -689,7 +689,7 @@
info.splitDependencies = pkg.getSplitDependencies().size() == 0
? null : pkg.getSplitDependencies();
- initForUser(info, pkg, userId);
+ initForUser(info, pkg, userId, state);
info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
@@ -1001,7 +1001,7 @@
}
private static void initForUser(ApplicationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid()));
@@ -1011,6 +1011,12 @@
return;
}
+ if (!pkg.isSystem() && state.getCeDataInode() <= 0) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1045,7 +1051,7 @@
// This duplicates the ApplicationInfo variant because it uses field assignment and the classes
// don't inherit from each other, unfortunately. Consolidating logic would introduce overhead.
private static void initForUser(InstrumentationInfo output, AndroidPackage input,
- @UserIdInt int userId) {
+ @UserIdInt int userId, PackageUserStateInternal state) {
PackageImpl pkg = ((PackageImpl) input);
String packageName = input.getPackageName();
if ("android".equals(packageName)) {
@@ -1053,6 +1059,12 @@
return;
}
+ if (!pkg.isSystem() && state.getCeDataInode() <= 0) {
+ // The data dir has been deleted
+ output.dataDir = null;
+ return;
+ }
+
// For performance reasons, all these paths are built as strings
if (userId == UserHandle.USER_SYSTEM) {
output.credentialProtectedDataDir =
@@ -1084,12 +1096,21 @@
}
}
- @NonNull
+ /**
+ * Returns the data dir of the app for the target user. Return null if the app isn't installed
+ * on the target user and doesn't have a data dir on the target user.
+ */
+ @Nullable
public static File getDataDir(PackageStateInternal ps, int userId) {
if ("android".equals(ps.getPackageName())) {
return Environment.getDataSystemDirectory();
}
+ if (!ps.isSystem() && ps.getUserStateOrDefault(userId).getCeDataInode() <= 0) {
+ // The data dir has been deleted
+ return null;
+ }
+
if (ps.isDefaultToDeviceProtectedStorage()
&& PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index f5ba3f6..d82a500 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -22,6 +22,7 @@
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.parsing.PackageLite;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
@@ -183,6 +184,23 @@
}
/**
+ * Creates a ParsedPackage from PackageLite without any additional parsing or processing.
+ * Most fields will get reasonable default values, corresponding to "deleted-keep-data".
+ */
+ @AnyThread
+ public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
+ throws PackageManagerException {
+ ParseInput input = mSharedResult.get().reset();
+ ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+ packageLite, flags);
+ if (result.isError()) {
+ throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+ result.getException());
+ }
+ return result.getResult().hideAsParsed();
+ }
+
+ /**
* Removes the cached value for the thread the parser was created on. It is assumed that
* any threads created for parallel parsing will be created and released, so they don't
* need an explicit close call.
diff --git a/services/core/java/com/android/server/pm/permission/TEST_MAPPING b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
index b2dcf37..24323c8 100644
--- a/services/core/java/com/android/server/pm/permission/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/permission/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index f1f0fa3..699ccbd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -375,6 +375,10 @@
ParsingPackage setBaseRevisionCode(int baseRevisionCode);
+ ParsingPackage setVersionCode(int vesionCode);
+
+ ParsingPackage setVersionCodeMajor(int vesionCodeMajor);
+
ParsingPackage setVersionName(String versionName);
ParsingPackage setCompileSdkVersion(int compileSdkVersion);
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index dc022f7..2d55b9f 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -474,15 +474,121 @@
}
}
- private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
- String codePath, SplitAssetLoader assetLoader, int flags) {
- final String apkPath = apkFile.getAbsolutePath();
+ /**
+ * Creates ParsingPackage using only PackageLite.
+ * Missing fields will contain reasonable defaults.
+ * Used for packageless (aka archived) package installation.
+ */
+ public ParseResult<ParsingPackage> parsePackageFromPackageLite(ParseInput input,
+ PackageLite lite, int flags) {
+ final String volumeUuid = getVolumeUuid(lite.getPath());
+ final String pkgName = lite.getPackageName();
+ final TypedArray manifestArray = null;
+ final ParsingPackage pkg = mCallback.startParsingPackage(pkgName,
+ lite.getBaseApkPath(), lite.getPath(), manifestArray, lite.isCoreApp());
+
+ final int targetSdk = lite.getTargetSdk();
+ final String versionName = null;
+ final int compileSdkVersion = 0;
+ final String compileSdkVersionCodeName = null;
+ final boolean isolatedSplitLoading = false;
+
+ // Normally set from manifestArray.
+ pkg.setVersionCode(lite.getVersionCode());
+ pkg.setVersionCodeMajor(lite.getVersionCodeMajor());
+ pkg.setBaseRevisionCode(lite.getBaseRevisionCode());
+ pkg.setVersionName(versionName);
+ pkg.setCompileSdkVersion(compileSdkVersion);
+ pkg.setCompileSdkVersionCodeName(compileSdkVersionCodeName);
+ pkg.setIsolatedSplitLoading(isolatedSplitLoading);
+ pkg.setTargetSdkVersion(targetSdk);
+
+ // parseBaseApkTags
+ pkg.setInstallLocation(lite.getInstallLocation())
+ .setTargetSandboxVersion(PARSE_DEFAULT_TARGET_SANDBOX)
+ /* Set the global "on SD card" flag */
+ .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+
+ // parseBaseAppBasicFlags
+ pkg
+ // Default true
+ .setBackupAllowed(lite.isBackupAllowed())
+ .setClearUserDataAllowed(lite.isClearUserDataAllowed())
+ .setClearUserDataOnFailedRestoreAllowed(
+ lite.isClearUserDataOnFailedRestoreAllowed())
+ .setAllowNativeHeapPointerTagging(true)
+ .setEnabled(true)
+ .setExtractNativeLibrariesRequested(true)
+ // targetSdkVersion gated
+ .setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q)
+ .setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage())
+ .setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P)
+ // Default false
+ .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage())
+ .setUserDataFragile(lite.isUserDataFragile())
+ // Ints
+ .setCategory(ApplicationInfo.CATEGORY_UNDEFINED)
+ // Floats Default 0f
+ .setMaxAspectRatio(0f)
+ .setMinAspectRatio(0f);
+
+ // No APK - no code.
+ pkg.setDeclaredHavingCode(false);
+
+ final String taskAffinity = null;
+ ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
+ pkgName, pkgName, taskAffinity, input);
+ if (taskAffinityResult.isError()) {
+ return input.error(taskAffinityResult);
+ }
+ pkg.setTaskAffinity(taskAffinityResult.getResult());
+
+ final CharSequence pname = null;
+ ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
+ pkgName, null /*defProc*/, pname, flags, mSeparateProcesses, input);
+ if (processNameResult.isError()) {
+ return input.error(processNameResult);
+ }
+ pkg.setProcessName(processNameResult.getResult());
+
+ pkg.setGwpAsanMode(-1);
+ pkg.setMemtagMode(-1);
+
+ afterParseBaseApplication(pkg);
+
+ final ParseResult<ParsingPackage> result = validateBaseApkTags(input, pkg);
+ if (result.isError()) {
+ return result;
+ }
+
+ pkg.setVolumeUuid(volumeUuid);
+
+ if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
+ pkg.setSigningDetails(lite.getSigningDetails());
+ } else {
+ pkg.setSigningDetails(SigningDetails.UNKNOWN);
+ }
+
+ return input.success(pkg
+ .set32BitAbiPreferred(lite.isUse32bitAbi()));
+ }
+
+ private static String getVolumeUuid(final String apkPath) {
String volumeUuid = null;
if (apkPath.startsWith(MNT_EXPAND)) {
final int end = apkPath.indexOf('/', MNT_EXPAND.length());
volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
}
+ return volumeUuid;
+ }
+
+ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
+ String codePath, SplitAssetLoader assetLoader, int flags) {
+ final String apkPath = apkFile.getAbsolutePath();
+
+ final String volumeUuid = getVolumeUuid(apkPath);
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
@@ -882,7 +988,7 @@
}
pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,
- R.styleable.AndroidManifest_installLocation, sa))
+ R.styleable.AndroidManifest_installLocation, sa))
.setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
@@ -932,6 +1038,10 @@
}
}
+ return validateBaseApkTags(input, pkg);
+ }
+
+ private ParseResult<ParsingPackage> validateBaseApkTags(ParseInput input, ParsingPackage pkg) {
if (!ParsedAttributionUtils.isCombinationValid(pkg.getAttributions())) {
return input.error(
INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -2199,15 +2309,19 @@
pkg.sortServices();
}
- // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
- // every activity info has had a chance to set it from its attributes.
+ afterParseBaseApplication(pkg);
+
+ return input.success(pkg);
+ }
+
+ // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
+ // every activity info has had a chance to set it from its attributes.
+ private void afterParseBaseApplication(ParsingPackage pkg) {
setMaxAspectRatio(pkg);
setMinAspectRatio(pkg);
setSupportsSizeChanges(pkg);
pkg.setHasDomainUrls(hasDomainURLs(pkg));
-
- return input.success(pkg);
}
/**
diff --git a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
index 65325c2..7c4d787 100644
--- a/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
+++ b/services/core/java/com/android/server/recoverysystem/hal/BootControlHIDL.java
@@ -22,6 +22,8 @@
import android.os.RemoteException;
import android.util.Slog;
+import java.util.NoSuchElementException;
+
public class BootControlHIDL implements IBootControl {
private static final String TAG = "BootControlHIDL";
@@ -32,7 +34,7 @@
public static boolean isServicePresent() {
try {
android.hardware.boot.V1_0.IBootControl.getService(true);
- } catch (RemoteException e) {
+ } catch (RemoteException | NoSuchElementException e) {
return false;
}
return true;
@@ -41,7 +43,7 @@
public static boolean isV1_2ServicePresent() {
try {
android.hardware.boot.V1_2.IBootControl.getService(true);
- } catch (RemoteException e) {
+ } catch (RemoteException | NoSuchElementException e) {
return false;
}
return true;
diff --git a/services/core/java/com/android/server/security/FileIntegrityService.java b/services/core/java/com/android/server/security/FileIntegrityService.java
index 9529621..3aed6e3 100644
--- a/services/core/java/com/android/server/security/FileIntegrityService.java
+++ b/services/core/java/com/android/server/security/FileIntegrityService.java
@@ -27,10 +27,12 @@
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
import android.security.IFileIntegrityService;
import android.util.Slog;
@@ -54,6 +56,7 @@
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
+import java.util.Objects;
/**
* A {@link SystemService} that provides file integrity related operations.
@@ -112,7 +115,7 @@
.exec(this, in, out, err, args, callback, resultReceiver);
}
- private void checkCallerPermission(String packageName) {
+ private void checkCallerPackageName(String packageName) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandle.getUserId(callingUid);
final PackageManagerInternal packageManager =
@@ -123,7 +126,10 @@
throw new SecurityException(
"Calling uid " + callingUid + " does not own package " + packageName);
}
+ }
+ private void checkCallerPermission(String packageName) {
+ checkCallerPackageName(packageName);
if (getContext().checkCallingPermission(android.Manifest.permission.INSTALL_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
return;
@@ -131,12 +137,43 @@
final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
final int mode = appOpsManager.checkOpNoThrow(
- AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, callingUid, packageName);
+ AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, Binder.getCallingUid(), packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
throw new SecurityException(
"Caller should have INSTALL_PACKAGES or REQUEST_INSTALL_PACKAGES");
}
}
+
+ @Override
+ public android.os.IInstalld.IFsveritySetupAuthToken createAuthToken(
+ ParcelFileDescriptor authFd) throws RemoteException {
+ Objects.requireNonNull(authFd);
+ try {
+ var authToken = getStorageManagerInternal().createFsveritySetupAuthToken(authFd,
+ Binder.getCallingUid(), Binder.getCallingUserHandle().getIdentifier());
+ // fs-verity setup requires no writable fd to the file. Release the dup now that
+ // it's passed.
+ authFd.close();
+ return authToken;
+ } catch (IOException e) {
+ throw new RemoteException(e);
+ }
+ }
+
+ @Override
+ public int setupFsverity(android.os.IInstalld.IFsveritySetupAuthToken authToken,
+ String filePath, String packageName) throws RemoteException {
+ Objects.requireNonNull(authToken);
+ Objects.requireNonNull(filePath);
+ Objects.requireNonNull(packageName);
+ checkCallerPackageName(packageName);
+
+ try {
+ return getStorageManagerInternal().enableFsverity(authToken, filePath, packageName);
+ } catch (IOException e) {
+ throw new RemoteException(e);
+ }
+ }
};
public FileIntegrityService(final Context context) {
@@ -146,9 +183,19 @@
} catch (CertificateException e) {
Slog.wtf(TAG, "Cannot get an instance of X.509 certificate factory");
}
+
LocalServices.addService(FileIntegrityService.class, this);
}
+ /**
+ * Returns StorageManagerInternal as a proxy to fs-verity related calls. This is to plumb
+ * the call through the canonical Installer instance in StorageManagerService, since the
+ * Installer instance isn't directly accessible.
+ */
+ private StorageManagerInternal getStorageManagerInternal() {
+ return LocalServices.getService(StorageManagerInternal.class);
+ }
+
@Override
public void onStart() {
loadAllCertificates();
diff --git a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
index f744d00..565eb6e 100644
--- a/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
+++ b/services/core/java/com/android/server/sensorprivacy/CameraPrivacyLightController.java
@@ -18,7 +18,6 @@
import static android.hardware.SensorManager.SENSOR_DELAY_NORMAL;
-import android.annotation.ColorInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.Sensor;
@@ -39,6 +38,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
import java.util.ArrayDeque;
@@ -48,12 +48,10 @@
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
-
class CameraPrivacyLightController implements AppOpsManager.OnOpActiveChangedListener,
SensorEventListener {
- @VisibleForTesting
- static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
+ private static final double LIGHT_VALUE_MULTIPLIER = 1 / Math.log(1.1);
private final Handler mHandler;
private final Executor mExecutor;
@@ -69,11 +67,6 @@
private LightsManager.LightsSession mLightsSession = null;
- @ColorInt
- private final int mDayColor;
- @ColorInt
- private final int mNightColor;
-
private final Sensor mLightSensor;
private boolean mIsAmbientLightListenerRegistered = false;
@@ -81,7 +74,9 @@
/** When average of the time integral over the past {@link #mMovingAverageIntervalMillis}
* milliseconds of the log_1.1(lux(t)) is greater than this value, use the daytime brightness
* else use nighttime brightness. */
- private final long mNightThreshold;
+ private final long[] mThresholds;
+
+ private final int[] mColors;
private final ArrayDeque<Pair<Long, Integer>> mAmbientLightValues = new ArrayDeque<>();
/** Tracks the Riemann sum of {@link #mAmbientLightValues} to avoid O(n) operations when sum is
* needed */
@@ -101,6 +96,20 @@
@VisibleForTesting
CameraPrivacyLightController(Context context, Looper looper) {
+ mColors = context.getResources().getIntArray(R.array.config_cameraPrivacyLightColors);
+ if (ArrayUtils.isEmpty(mColors)) {
+ mHandler = null;
+ mExecutor = null;
+ mContext = null;
+ mAppOpsManager = null;
+ mLightsManager = null;
+ mSensorManager = null;
+ mLightSensor = null;
+ mMovingAverageIntervalMillis = 0;
+ mThresholds = null;
+ // Return here before this class starts interacting with other services.
+ return;
+ }
mContext = context;
mHandler = new Handler(looper);
@@ -109,14 +118,20 @@
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mLightsManager = mContext.getSystemService(LightsManager.class);
mSensorManager = mContext.getSystemService(SensorManager.class);
-
- mDayColor = mContext.getColor(R.color.camera_privacy_light_day);
- mNightColor = mContext.getColor(R.color.camera_privacy_light_night);
mMovingAverageIntervalMillis = mContext.getResources()
.getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
- mNightThreshold = (long) (Math.log(mContext.getResources()
- .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold))
- * LIGHT_VALUE_MULTIPLIER);
+ int[] thresholdsLux = mContext.getResources().getIntArray(
+ R.array.config_cameraPrivacyLightAlsLuxThresholds);
+ if (thresholdsLux.length != mColors.length - 1) {
+ throw new IllegalStateException("There must be exactly one more color than thresholds."
+ + " Found " + mColors.length + " colors and " + thresholdsLux.length
+ + " thresholds.");
+ }
+ mThresholds = new long[thresholdsLux.length];
+ for (int i = 0; i < thresholdsLux.length; i++) {
+ int luxValue = thresholdsLux[i];
+ mThresholds[i] = (long) (Math.log(luxValue) * LIGHT_VALUE_MULTIPLIER);
+ }
List<Light> lights = mLightsManager.getLights();
for (int i = 0; i < lights.size(); i++) {
@@ -223,13 +238,8 @@
mLightsSession.close();
mLightsSession = null;
} else {
- int lightColor;
- if (mLightSensor != null && getLiveAmbientLightTotal()
- < getCurrentIntervalMillis() * mNightThreshold) {
- lightColor = mNightColor;
- } else {
- lightColor = mDayColor;
- }
+ int lightColor =
+ mLightSensor == null ? mColors[mColors.length - 1] : computeCurrentLightColor();
if (mLastLightColor == lightColor && mLightsSession != null) {
return;
@@ -252,6 +262,18 @@
}
}
+ private int computeCurrentLightColor() {
+ long liveAmbientLightTotal = getLiveAmbientLightTotal();
+ long currentInterval = getCurrentIntervalMillis();
+
+ for (int i = 0; i < mThresholds.length; i++) {
+ if (liveAmbientLightTotal < currentInterval * mThresholds[i]) {
+ return mColors[i];
+ }
+ }
+ return mColors[mColors.length - 1];
+ }
+
private void updateSensorListener(boolean shouldSessionEnd) {
if (shouldSessionEnd && mIsAmbientLightListenerRegistered) {
mSensorManager.unregisterListener(this);
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 415d05e..0043122 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -120,9 +120,6 @@
private final BroadcastReceiver mTrustableDowngradeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) {
- return;
- }
// are these the broadcasts we want to listen to
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
downgradeToTrustable();
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index bed69fc..cc95da5 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -58,7 +58,6 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -165,10 +164,6 @@
@GuardedBy("mUserIsTrusted")
private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray();
- //TODO(b/215724686): remove flag
- public static final boolean ENABLE_ACTIVE_UNLOCK_FLAG = SystemProperties.getBoolean(
- "fw.enable_active_unlock_flag", true);
-
private enum TrustState {
UNTRUSTED, // the phone is not unlocked by any trustagents
TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
@@ -569,69 +564,6 @@
int flags,
boolean isFromUnlock,
@Nullable AndroidFuture<GrantTrustResult> resultCallback) {
- if (ENABLE_ACTIVE_UNLOCK_FLAG) {
- updateTrustWithRenewableUnlock(userId, flags, isFromUnlock, resultCallback);
- } else {
- updateTrustWithNonrenewableTrust(userId, flags, isFromUnlock);
- }
- }
-
- private void updateTrustWithNonrenewableTrust(int userId, int flags, boolean isFromUnlock) {
- boolean managed = aggregateIsTrustManaged(userId);
- dispatchOnTrustManagedChanged(managed, userId);
- if (mStrongAuthTracker.isTrustAllowedForUser(userId)
- && isTrustUsuallyManagedInternal(userId) != managed) {
- updateTrustUsuallyManaged(userId, managed);
- }
-
- boolean trusted = aggregateIsTrusted(userId);
- IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- boolean showingKeyguard = true;
- try {
- showingKeyguard = wm.isKeyguardLocked();
- } catch (RemoteException e) {
- }
-
- boolean changed;
- synchronized (mUserIsTrusted) {
- if (mSettingsObserver.getTrustAgentsNonrenewableTrust()) {
- // For non-renewable trust agents can only set the device to trusted if it already
- // trusted or the device is unlocked. Attempting to set the device as trusted
- // when the device is locked will be ignored.
- changed = mUserIsTrusted.get(userId) != trusted;
- trusted = trusted
- && (!showingKeyguard || isFromUnlock || !changed)
- && userId == mCurrentUser;
- if (DEBUG) {
- Slog.d(TAG, "Extend unlock setting trusted as " + Boolean.toString(trusted)
- + " && " + Boolean.toString(!showingKeyguard)
- + " && " + Boolean.toString(userId == mCurrentUser));
- }
- }
- changed = mUserIsTrusted.get(userId) != trusted;
- mUserIsTrusted.put(userId, trusted);
- }
- dispatchOnTrustChanged(
- trusted,
- false /* newlyUnlocked */,
- userId,
- flags,
- getTrustGrantedMessages(userId));
- if (changed) {
- refreshDeviceLockedForUser(userId);
- if (!trusted) {
- maybeLockScreen(userId);
- } else {
- scheduleTrustTimeout(false /* override */, false /* isTrustableTimeout*/);
- }
- }
- }
-
- private void updateTrustWithRenewableUnlock(
- int userId,
- int flags,
- boolean isFromUnlock,
- @Nullable AndroidFuture<GrantTrustResult> resultCallback) {
boolean managed = aggregateIsTrustManaged(userId);
dispatchOnTrustManagedChanged(managed, userId);
if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -2265,16 +2197,11 @@
@Override
public void handleAlarm() {
- TrustableTimeoutAlarmListener otherAlarm;
- boolean otherAlarmPresent;
- if (ENABLE_ACTIVE_UNLOCK_FLAG) {
- otherAlarm = mTrustableTimeoutAlarmListenerForUser.get(mUserId);
- otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
- if (otherAlarmPresent) {
- synchronized (mAlarmLock) {
- disableNonrenewableTrustWhileRenewableTrustIsPresent();
- }
- return;
+ TrustableTimeoutAlarmListener otherAlarm =
+ mTrustableTimeoutAlarmListenerForUser.get(mUserId);
+ if (otherAlarm != null && otherAlarm.isQueued()) {
+ synchronized (mAlarmLock) {
+ disableNonrenewableTrustWhileRenewableTrustIsPresent();
}
}
}
@@ -2299,17 +2226,11 @@
@Override
public void handleAlarm() {
- TrustedTimeoutAlarmListener otherAlarm;
- boolean otherAlarmPresent;
- if (ENABLE_ACTIVE_UNLOCK_FLAG) {
- cancelBothTrustableAlarms(mUserId);
- otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
- otherAlarmPresent = (otherAlarm != null) && otherAlarm.isQueued();
- if (otherAlarmPresent) {
- synchronized (mAlarmLock) {
- disableRenewableTrustWhileNonrenewableTrustIsPresent();
- }
- return;
+ cancelBothTrustableAlarms(mUserId);
+ TrustedTimeoutAlarmListener otherAlarm = mTrustTimeoutAlarmListenerForUser.get(mUserId);
+ if (otherAlarm != null && otherAlarm.isQueued()) {
+ synchronized (mAlarmLock) {
+ disableRenewableTrustWhileNonrenewableTrustIsPresent();
}
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6a2b824..2559b84 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -41,6 +41,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
@@ -94,6 +95,7 @@
import android.view.InputChannel;
import android.view.Surface;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
@@ -177,6 +179,11 @@
private final ActivityManager mActivityManager;
+ private boolean mExternalInputLoggingDisplayNameFilterEnabled = false;
+ private final HashSet<String> mExternalInputLoggingDeviceOnScreenDisplayNames =
+ new HashSet<String>();
+ private final List<String> mExternalInputLoggingDeviceBrandNames = new ArrayList<String>();
+
public TvInputManagerService(Context context) {
super(context);
@@ -192,6 +199,8 @@
synchronized (mLock) {
getOrCreateUserStateLocked(mCurrentUserId);
}
+
+ initExternalInputLoggingConfigs();
}
@Override
@@ -224,6 +233,21 @@
}
}
+ private void initExternalInputLoggingConfigs() {
+ mExternalInputLoggingDisplayNameFilterEnabled = mContext.getResources().getBoolean(
+ R.bool.config_tvExternalInputLoggingDisplayNameFilterEnabled);
+ if (!mExternalInputLoggingDisplayNameFilterEnabled) {
+ return;
+ }
+ final String[] deviceOnScreenDisplayNames = mContext.getResources().getStringArray(
+ R.array.config_tvExternalInputLoggingDeviceOnScreenDisplayNames);
+ final String[] deviceBrandNames = mContext.getResources().getStringArray(
+ R.array.config_tvExternalInputLoggingDeviceBrandNames);
+ mExternalInputLoggingDeviceOnScreenDisplayNames.addAll(
+ Arrays.asList(deviceOnScreenDisplayNames));
+ mExternalInputLoggingDeviceBrandNames.addAll(Arrays.asList(deviceBrandNames));
+ }
+
private void registerBroadcastReceivers() {
PackageMonitor monitor = new PackageMonitor() {
private void buildTvInputList(String[] packages) {
@@ -3073,6 +3097,9 @@
hdmiPort = hdmiDeviceInfo.getPortId();
if (hdmiDeviceInfo.isCecDevice()) {
displayName = hdmiDeviceInfo.getDisplayName();
+ if (mExternalInputLoggingDisplayNameFilterEnabled) {
+ displayName = filterExternalInputLoggingDisplayName(displayName);
+ }
vendorId = hdmiDeviceInfo.getVendorId();
}
}
@@ -3082,6 +3109,22 @@
inputType, vendorId, hdmiPort, tifSessionId, displayName);
}
+ private String filterExternalInputLoggingDisplayName(String displayName) {
+ String nullDisplayName = "NULL_DISPLAY_NAME", filteredDisplayName = "FILTERED_DISPLAY_NAME";
+ if (displayName == null) {
+ return nullDisplayName;
+ }
+ if (mExternalInputLoggingDeviceOnScreenDisplayNames.contains(displayName)) {
+ return displayName;
+ }
+ for (String brandName : mExternalInputLoggingDeviceBrandNames) {
+ if (displayName.toUpperCase().contains(brandName.toUpperCase())) {
+ return brandName;
+ }
+ }
+ return filteredDisplayName;
+ }
+
private static final class UserState {
// A mapping from the TV input id to its TvInputState.
private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -3429,6 +3472,10 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+ if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
+ return;
+ }
mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo);
addHardwareInputLocked(inputInfo);
}
@@ -3443,6 +3490,10 @@
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+ if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) {
+ return;
+ }
mTvInputHardwareManager.addHdmiInput(id, inputInfo);
addHardwareInputLocked(inputInfo);
if (mOnScreenInputId != null && mOnScreenSessionState != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 598c1ae..ddc0519 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2000,7 +2000,12 @@
WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
if (serviceInfo == null) {
- clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+ if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
+ clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, null);
+ clearWallpaperLocked(FLAG_LOCK, wallpaper.userId, reply);
+ } else {
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+ }
return;
}
Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2032,7 +2037,7 @@
WallpaperData data = null;
synchronized (mLock) {
if (mIsLockscreenLiveWallpaperEnabled) {
- clearWallpaperLocked(callingPackage, which, userId, null);
+ clearWallpaperLocked(callingPackage, which, userId);
} else {
clearWallpaperLocked(which, userId, null);
}
@@ -2052,8 +2057,7 @@
}
}
- private void clearWallpaperLocked(String callingPackage, int which, int userId,
- IRemoteCallback reply) {
+ private void clearWallpaperLocked(String callingPackage, int which, int userId) {
// Might need to bring it in the first time to establish our rewrite
if (!mWallpaperMap.contains(userId)) {
@@ -2092,8 +2096,8 @@
finalWhich = which;
}
- boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
- component, callingPackage, finalWhich, userId, reply));
+ boolean success = withCleanCallingIdentity(() -> setWallpaperComponent(
+ component, callingPackage, finalWhich, userId));
if (success) return;
} catch (IllegalArgumentException e1) {
e = e1;
@@ -2105,23 +2109,10 @@
// wallpaper.
Slog.e(TAG, "Default wallpaper component not found!", e);
withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
- if (reply != null) {
- try {
- reply.sendResult(null);
- } catch (RemoteException e1) {
- Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
- }
- }
}
+ // TODO(b/266818039) remove this version of the method
private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid());
- clearWallpaperLocked(callingPackage, which, userId, reply);
- return;
- }
-
if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
}
@@ -3293,7 +3284,7 @@
boolean setWallpaperComponent(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
if (mIsLockscreenLiveWallpaperEnabled) {
- return setWallpaperComponentInternal(name, callingPackage, which, userId, null);
+ return setWallpaperComponentInternal(name, callingPackage, which, userId);
} else {
setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
return true;
@@ -3301,7 +3292,7 @@
}
private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage,
- @SetWallpaperFlags int which, int userIdIn, IRemoteCallback reply) {
+ @SetWallpaperFlags int which, int userIdIn) {
if (DEBUG) {
Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
}
@@ -3350,7 +3341,6 @@
Slog.d(TAG, "publish system wallpaper changed!");
}
liveSync.complete();
- if (reply != null) reply.sendResult(null);
}
};
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9eec5f8..90e67df 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -225,7 +225,6 @@
import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_COPY_TO_CLIENT;
-import static com.android.server.wm.StartingData.AFTER_TRANSACTION_IDLE;
import static com.android.server.wm.StartingData.AFTER_TRANSACTION_REMOVE_DIRECTLY;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -2698,7 +2697,8 @@
private void requestCopySplashScreen() {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_COPYING;
- if (!mAtmService.mTaskOrganizerController.copySplashScreenView(getTask())) {
+ if (mStartingSurface == null || !mAtmService.mTaskOrganizerController.copySplashScreenView(
+ getTask(), mStartingSurface.mTaskOrganizer)) {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_FINISH;
removeStartingWindow();
}
@@ -2711,12 +2711,14 @@
*/
void onCopySplashScreenFinish(SplashScreenViewParcelable parcelable) {
removeTransferSplashScreenTimeout();
- // unable to copy from shell, maybe it's not a splash screen. or something went wrong.
- // either way, abort and reset the sequence.
- if (parcelable == null
+ final SurfaceControl windowAnimationLeash = (parcelable == null
|| mTransferringSplashScreenState != TRANSFER_SPLASH_SCREEN_COPYING
|| mStartingWindow == null || mStartingWindow.mRemoved
- || finishing) {
+ || finishing) ? null
+ : TaskOrganizerController.applyStartingWindowAnimation(mStartingWindow);
+ if (windowAnimationLeash == null) {
+ // Unable to copy from shell, maybe it's not a splash screen, or something went wrong.
+ // Either way, abort and reset the sequence.
if (parcelable != null) {
parcelable.clearIfNeeded();
}
@@ -2724,9 +2726,6 @@
removeStartingWindow();
return;
}
- // schedule attach splashScreen to client
- final SurfaceControl windowAnimationLeash = TaskOrganizerController
- .applyStartingWindowAnimation(mStartingWindow);
try {
mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
@@ -2766,7 +2765,8 @@
&& (mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
|| mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_IDLE)) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Cleaning splash screen token=%s", this);
- mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask());
+ mAtmService.mTaskOrganizerController.onAppSplashScreenViewRemoved(getTask(),
+ mStartingSurface != null ? mStartingSurface.mTaskOrganizer : null);
}
}
@@ -2854,7 +2854,6 @@
} else if (lastData.mRemoveAfterTransaction == AFTER_TRANSACTION_COPY_TO_CLIENT) {
removeStartingWindow();
}
- lastData.mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
}
void removeStartingWindowAnimation(boolean prepareAnimation) {
@@ -3364,7 +3363,11 @@
// current focused activity could be another activity in the same Task if activities are
// displayed on adjacent TaskFragments.
final ActivityRecord currentFocusedApp = mDisplayContent.mFocusedApp;
- if (currentFocusedApp != null && currentFocusedApp.task == task) {
+ final int topFocusedDisplayId = mRootWindowContainer.getTopFocusedDisplayContent() != null
+ ? mRootWindowContainer.getTopFocusedDisplayContent().getDisplayId()
+ : INVALID_DISPLAY;
+ if (currentFocusedApp != null && currentFocusedApp.task == task
+ && topFocusedDisplayId == mDisplayContent.getDisplayId()) {
final Task topFocusableTask = mDisplayContent.getTask(
(t) -> t.isLeafTask() && t.isFocusable(), true /* traverseTopToBottom */);
if (task == topFocusableTask) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59159bb..42c3630 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4048,10 +4048,6 @@
mRecentTasks.notifyTaskPersisterLocked(task, flush);
}
- boolean isKeyguardLocked(int displayId) {
- return mKeyguardController.isKeyguardLocked(displayId);
- }
-
/**
* Clears launch params for the given package.
*
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index 4180cd2..15a0445 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -40,8 +40,6 @@
private static final boolean DEBUG = false;
// Desktop mode feature flags.
- private static final boolean DESKTOP_MODE_PROTO1_SUPPORTED =
- SystemProperties.getBoolean("persist.wm.debug.desktop_mode", false);
private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
// Override default freeform task width when desktop mode is enabled. In dips.
@@ -142,6 +140,6 @@
/** Whether desktop mode is supported. */
static boolean isDesktopModeSupported() {
- return DESKTOP_MODE_PROTO1_SUPPORTED || DESKTOP_MODE_PROTO2_SUPPORTED;
+ return DESKTOP_MODE_PROTO2_SUPPORTED;
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 8c1d8fa..1a319ad 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -423,7 +423,7 @@
final TransitionController tc = mRootWindowContainer.mTransitionController;
- final boolean occluded = isDisplayOccluded(displayId);
+ final boolean occluded = getDisplayState(displayId).mOccluded;
final boolean performTransition = isKeyguardLocked(displayId);
final boolean executeTransition = performTransition && !tc.isCollecting();
@@ -500,15 +500,6 @@
}
}
- /**
- * Returns {@code true} if the top activity on the display can occlude keyguard or the device
- * is dreaming. Note that this method may return {@code true} even if the keyguard is disabled
- * or not showing.
- */
- boolean isDisplayOccluded(int displayId) {
- return getDisplayState(displayId).mOccluded;
- }
-
ActivityRecord getTopOccludingActivity(int displayId) {
return getDisplayState(displayId).mTopOccludesActivity;
}
@@ -601,6 +592,11 @@
private boolean mAodShowing;
private boolean mKeyguardGoingAway;
private boolean mDismissalRequested;
+
+ /**
+ * True if the top activity on the display can occlude keyguard or the device is dreaming.
+ * Note that this can be true even if the keyguard is disabled or not showing.
+ */
private boolean mOccluded;
private boolean mShowingDream;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6b4cc25..57f8268 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2699,7 +2699,7 @@
// transition exists, so this affects only when no lock screen is set. Otherwise
// keyguard going away animation will be played.
// See also AppTransitionController#getTransitCompatType for more details.
- if ((!mTaskSupervisor.getKeyguardController().isDisplayOccluded(display.mDisplayId)
+ if ((!mTaskSupervisor.getKeyguardController().isKeyguardOccluded(display.mDisplayId)
&& token.mTag.equals(KEYGUARD_SLEEP_TOKEN_TAG))
|| token.mTag.equals(DISPLAY_OFF_SLEEP_TOKEN_TAG)) {
display.mSkipAppTransitionAnimation = true;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index a23547e..2d281c4 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -76,7 +76,7 @@
* This starting window should be removed after applying the start transaction of transition,
* which ensures the app window has shown.
*/
- @AfterTransaction int mRemoveAfterTransaction;
+ @AfterTransaction int mRemoveAfterTransaction = AFTER_TRANSACTION_IDLE;
/** Whether to prepare the removal animation. */
boolean mPrepareRemoveAnimation;
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 0bb773a..a55c232 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -40,6 +40,7 @@
import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
import android.util.Slog;
+import android.window.ITaskOrganizer;
import android.window.SplashScreenView;
import android.window.TaskSnapshot;
@@ -79,12 +80,13 @@
}
StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
-
synchronized (mService.mGlobalLock) {
final Task task = activity.getTask();
- if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(
- task, activity, theme, null /* taskSnapshot */)) {
- return new StartingSurface(task);
+ final TaskOrganizerController controller =
+ mService.mAtmService.mTaskOrganizerController;
+ if (task != null && controller.addStartingWindow(task, activity, theme,
+ null /* taskSnapshot */)) {
+ return new StartingSurface(task, controller.getTaskOrganizer());
}
}
return null;
@@ -166,9 +168,12 @@
activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
activity, false /* checkOpening */);
}
- mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
- activity, 0 /* launchTheme */, taskSnapshot);
- return new StartingSurface(task);
+ final TaskOrganizerController controller =
+ mService.mAtmService.mTaskOrganizerController;
+ if (controller.addStartingWindow(task, activity, 0 /* launchTheme */, taskSnapshot)) {
+ return new StartingSurface(task, controller.getTaskOrganizer());
+ }
+ return null;
}
}
@@ -256,9 +261,12 @@
final class StartingSurface {
private final Task mTask;
+ // The task organizer which hold the client side reference of this surface.
+ final ITaskOrganizer mTaskOrganizer;
- StartingSurface(Task task) {
+ StartingSurface(Task task, ITaskOrganizer taskOrganizer) {
mTask = task;
+ mTaskOrganizer = taskOrganizer;
}
/**
@@ -268,7 +276,8 @@
*/
public void remove(boolean animate) {
synchronized (mService.mGlobalLock) {
- mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate);
+ mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
+ mTaskOrganizer, animate);
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f9bbc68..387a876 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3579,7 +3579,7 @@
&& activity.info != info.taskInfo.topActivityInfo
? activity.info : null;
info.isKeyguardOccluded =
- mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY);
+ mAtmService.mKeyguardController.isKeyguardOccluded(info.taskInfo.displayId);
info.startingWindowTypeParameter = activity.mStartingData != null
? activity.mStartingData.mTypeParams
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3d01001..41e49b9 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -652,7 +652,7 @@
if (rootTask == null || activity.mStartingData == null) {
return false;
}
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ final ITaskOrganizer lastOrganizer = getTaskOrganizer();
if (lastOrganizer == null) {
return false;
}
@@ -672,12 +672,13 @@
return true;
}
- void removeStartingWindow(Task task, boolean prepareAnimation) {
+ void removeStartingWindow(Task task, ITaskOrganizer taskOrganizer, boolean prepareAnimation) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
return;
}
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer
+ : getTaskOrganizer();
if (lastOrganizer == null) {
return;
}
@@ -771,12 +772,13 @@
}
}
- boolean copySplashScreenView(Task task) {
+ boolean copySplashScreenView(Task task, ITaskOrganizer taskOrganizer) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
return false;
}
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ final ITaskOrganizer lastOrganizer = taskOrganizer != null ? taskOrganizer
+ : getTaskOrganizer();
if (lastOrganizer == null) {
return false;
}
@@ -799,12 +801,12 @@
* @see com.android.wm.shell.ShellTaskOrganizer#onAppSplashScreenViewRemoved(int)
* @see SplashScreenView#remove()
*/
- public void onAppSplashScreenViewRemoved(Task task) {
+ public void onAppSplashScreenViewRemoved(Task task, ITaskOrganizer organizer) {
final Task rootTask = task.getRootTask();
if (rootTask == null) {
return;
}
- final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+ final ITaskOrganizer lastOrganizer = organizer != null ? organizer : getTaskOrganizer();
if (lastOrganizer == null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index e9af42b..81f91c7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1127,6 +1127,9 @@
// though, notify the controller to prevent degenerate cases.
if (!r.isVisibleRequested()) {
mController.mValidateCommitVis.add(r);
+ } else {
+ // Make sure onAppTransitionFinished can be notified.
+ mParticipants.add(r);
}
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 31afcbf..4d73358 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -519,7 +519,8 @@
for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) {
final ActivityRecord r =
mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord();
- if (r != null && r.isInTransition()) {
+ // Only care about the transition at Activity/Task level.
+ if (r != null && r.inTransitionSelfOrParent() && !r.mDisplayContent.inTransition()) {
return true;
}
}
diff --git a/services/core/jni/gnss/GnssVisibilityControlCallback.cpp b/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
index ec215f1..bc57c1d 100644
--- a/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
+++ b/services/core/jni/gnss/GnssVisibilityControlCallback.cpp
@@ -73,7 +73,7 @@
template <>
jstring ToJstring(JNIEnv* env, const String16& value) {
- const char16_t* str = value.string();
+ const char16_t* str = value.c_str();
size_t len = value.size();
return env->NewString(reinterpret_cast<const jchar*>(str), len);
}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index ea5ce65..49962ea 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -52,7 +52,6 @@
public static final int IPV6_ADDR_BITS = 128;
public static final int IPV6_ADDR_LEN = 16;
public static final int IPV6_MIN_MTU = 1280;
- public static final int RFC7421_PREFIX_LENGTH = 64;
/**
* ICMP common (v4/v6) constants.
diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING
index b2dcf37..24323c8 100644
--- a/services/permission/TEST_MAPPING
+++ b/services/permission/TEST_MAPPING
@@ -4,7 +4,7 @@
"name": "CtsPermissionTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
@@ -32,7 +32,7 @@
"name": "CtsPermissionPolicyTestCases",
"options": [
{
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
"include-filter": "android.permissionpolicy.cts.RestrictedPermissionsTest"
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index e2939c1..98c6c42 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -612,6 +612,9 @@
final PackageSetting pkgSetting = scanResult.mPkgSetting;
assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
+ // pretend that the data dir has been set up already, so that the generated applicationInfo
+ // includes the expected data dir string
+ pkgSetting.setCeDataInode(/* ceDataInode= */100, /* userId= */0);
final ApplicationInfo applicationInfo = PackageInfoUtils.generateApplicationInfo(
pkgSetting.getPkg(), 0, pkgSetting.getUserStateOrDefault(0), 0, pkgSetting);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
similarity index 74%
rename from services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index a8b0a7b..c7e1bda 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ArchiveManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -37,10 +37,9 @@
import android.content.pm.VersionedPackage;
import android.os.Binder;
import android.os.Build;
-import android.os.Process;
+import android.os.ParcelableException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.text.TextUtils;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -62,7 +61,7 @@
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
-public class ArchiveManagerTest {
+public class PackageArchiverServiceTest {
private static final String PACKAGE = "com.example";
private static final String CALLER_PACKAGE = "com.vending";
@@ -95,10 +94,11 @@
private final List<LauncherActivityInfo> mLauncherActivityInfos = createLauncherActivities();
+ private final int mUserId = UserHandle.CURRENT.getIdentifier();
+
private PackageSetting mPackageSetting;
- private PackageManagerService mPm;
- private ArchiveManager mArchiveManager;
+ private PackageArchiverService mArchiveService;
@Before
public void setUp() throws Exception {
@@ -106,7 +106,7 @@
mMockSystem.system().stageNominalSystemState();
when(mMockSystem.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
- mPm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
+ PackageManagerService pm = spy(new PackageManagerService(mMockSystem.mocks().getInjector(),
/* factoryTest= */false,
MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
/* isEngBuild= */ false,
@@ -124,37 +124,42 @@
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
- doReturn(mComputer).when(mPm).snapshotComputer();
- when(mComputer.getNameForUid(eq(Binder.getCallingUid()))).thenReturn(CALLER_PACKAGE);
- mArchiveManager = new ArchiveManager(mContext, mPm);
+ doReturn(mComputer).when(pm).snapshotComputer();
+ when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
+ Binder.getCallingUid());
+ mArchiveService = new PackageArchiverService(mContext, pm);
}
@Test
public void archiveApp_callerPackageNameIncorrect() {
Exception e = assertThrows(
SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, "different", UserHandle.CURRENT,
- mIntentSender)
+ () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
+ UserHandle.CURRENT
+ )
);
assertThat(e).hasMessageThat().isEqualTo(
String.format(
- "The callerPackageName %s set by the caller doesn't match the "
- + "caller's own package name %s.",
- "different",
- CALLER_PACKAGE));
+ "The UID %s of callerPackageName set by the caller doesn't match the "
+ + "caller's actual UID %s.",
+ 0,
+ Binder.getCallingUid()));
}
@Test
public void archiveApp_packageNotInstalled() {
- when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
null);
Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
}
@Test
@@ -162,11 +167,14 @@
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
Exception e = assertThrows(
- PackageManager.NameNotFoundException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT,
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(String.format("Package %s not found.", PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
}
@Test
@@ -183,17 +191,18 @@
when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
Exception e = assertThrows(
- SecurityException.class,
- () -> mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, Process.myUserHandle(),
- mIntentSender)
+ ParcelableException.class,
+ () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT
+ )
);
- assertThat(e).hasMessageThat().isEqualTo(
- TextUtils.formatSimple("No installer found to archive app %s.",
- PACKAGE));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("No installer found to archive app %s.", PACKAGE));
}
@Test
- public void archiveApp_success() throws PackageManager.NameNotFoundException {
+ public void archiveApp_success() {
List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
// TODO(b/278553670) Extract and store launcher icons
@@ -203,7 +212,8 @@
activityInfos.add(activityInfo);
}
- mArchiveManager.archiveApp(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT, mIntentSender);
+ mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE), eq(DELETE_KEEP_DATA), eq(mIntentSender),
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
index d99af77..e5fae49 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageFreezerTest.kt
@@ -88,7 +88,8 @@
@Test
fun freezePackage() {
- val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms, TEST_EXIT_REASON)
+ val freezer = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(1))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
@@ -104,9 +105,9 @@
@Test
fun freezePackage_twice() {
val freezer1 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
val freezer2 = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(2))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
@@ -127,7 +128,7 @@
@Test
fun freezePackage_withoutClosing() {
var freezer: PackageFreezer? = PackageFreezer(TEST_PACKAGE, TEST_USER_ID, TEST_REASON, pms,
- TEST_EXIT_REASON)
+ TEST_EXIT_REASON, null /* request */)
verify(pms, times(1))
.killApplication(eq(TEST_PACKAGE), any(), eq(TEST_USER_ID), eq(TEST_REASON),
eq(TEST_EXIT_REASON))
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
index 20cfd59..dc04b6a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/CameraPrivacyLightControllerTest.java
@@ -24,10 +24,12 @@
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.AppOpsManager;
import android.content.Context;
@@ -43,14 +45,13 @@
import android.os.Handler;
import android.os.Looper;
import android.permission.PermissionManager;
-import android.util.ArraySet;
+import android.testing.TestableLooper;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.internal.R;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -62,24 +63,19 @@
import java.util.List;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class CameraPrivacyLightControllerTest {
+ private int[] mDefaultColors = {0, 1, 2};
+ private int[] mDefaultAlsThresholdsLux = {10, 50};
+ private int mDefaultAlsAveragingIntervalMillis = 5000;
- private int mDayColor = 1;
- private int mNightColor = 0;
- private int mCameraPrivacyLightAlsAveragingIntervalMillis = 5000;
- private int mCameraPrivacyLightAlsNightThreshold = (int) getLightSensorValue(15);
+ private TestableLooper mTestableLooper;
private MockitoSession mMockitoSession;
@Mock
- private Context mContext;
-
- @Mock
- private Resources mResources;
-
- @Mock
private LightsManager mLightsManager;
@Mock
@@ -103,11 +99,64 @@
private ArgumentCaptor<SensorEventListener> mLightSensorListenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
- private Set<String> mExemptedPackages = new ArraySet<>();
- private List<Light> mLights = new ArrayList<>();
+ private Set<String> mExemptedPackages;
+ private List<Light> mLights;
private int mNextLightId = 1;
+ public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController() {
+ return prepareDefaultCameraPrivacyLightController(List.of(getNextLight(true)));
+ }
+
+ public CameraPrivacyLightController prepareDefaultCameraPrivacyLightController(
+ List<Light> lights) {
+ return prepareCameraPrivacyLightController(lights, Set.of(), true, mDefaultColors,
+ mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
+ }
+
+ public CameraPrivacyLightController prepareCameraPrivacyLightController(List<Light> lights,
+ Set<String> exemptedPackages, boolean hasLightSensor, int[] lightColors,
+ int[] alsThresholds, int averagingInterval) {
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ Looper.prepare();
+ looper = Looper.myLooper();
+ }
+ if (mTestableLooper == null) {
+ try {
+ mTestableLooper = new TestableLooper(looper);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Context context = mock(Context.class);
+ Resources resources = mock(Resources.class);
+ doReturn(resources).when(context).getResources();
+ doReturn(lightColors).when(resources).getIntArray(R.array.config_cameraPrivacyLightColors);
+ doReturn(alsThresholds).when(resources)
+ .getIntArray(R.array.config_cameraPrivacyLightAlsLuxThresholds);
+ doReturn(averagingInterval).when(resources)
+ .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
+
+ doReturn(mLightsManager).when(context).getSystemService(LightsManager.class);
+ doReturn(mAppOpsManager).when(context).getSystemService(AppOpsManager.class);
+ doReturn(mSensorManager).when(context).getSystemService(SensorManager.class);
+
+ mLights = lights;
+ mExemptedPackages = exemptedPackages;
+ doReturn(mLights).when(mLightsManager).getLights();
+ doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
+ if (!hasLightSensor) {
+ mLightSensor = null;
+ }
+ doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
+ doReturn(exemptedPackages)
+ .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
+
+ return new CameraPrivacyLightController(context, looper);
+ }
+
@Before
public void setUp() {
mMockitoSession = ExtendedMockito.mockitoSession()
@@ -115,49 +164,33 @@
.strictness(Strictness.WARN)
.spyStatic(PermissionManager.class)
.startMocking();
-
- doReturn(mDayColor).when(mContext).getColor(R.color.camera_privacy_light_day);
- doReturn(mNightColor).when(mContext).getColor(R.color.camera_privacy_light_night);
-
- doReturn(mResources).when(mContext).getResources();
- doReturn(mCameraPrivacyLightAlsAveragingIntervalMillis).when(mResources)
- .getInteger(R.integer.config_cameraPrivacyLightAlsAveragingIntervalMillis);
- doReturn(mCameraPrivacyLightAlsNightThreshold).when(mResources)
- .getInteger(R.integer.config_cameraPrivacyLightAlsNightThreshold);
-
- doReturn(mLightsManager).when(mContext).getSystemService(LightsManager.class);
- doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
- doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
-
- doReturn(mLights).when(mLightsManager).getLights();
- doReturn(mLightsSession).when(mLightsManager).openSession(anyInt());
- doReturn(mLightSensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- doReturn(mExemptedPackages)
- .when(() -> PermissionManager.getIndicatorExemptedPackages(any()));
}
@After
public void tearDown() {
- mExemptedPackages.clear();
- mLights.clear();
-
mMockitoSession.finishMocking();
}
@Test
+ public void testNoInteractionsWithServicesIfNoColorsSpecified() {
+ prepareCameraPrivacyLightController(List.of(getNextLight(true)), Collections.EMPTY_SET,
+ true, new int[0], mDefaultAlsThresholdsLux, mDefaultAlsAveragingIntervalMillis);
+
+ verifyZeroInteractions(mLightsManager);
+ verifyZeroInteractions(mAppOpsManager);
+ verifyZeroInteractions(mSensorManager);
+ }
+
+ @Test
public void testAppsOpsListenerNotRegisteredWithoutCameraLights() {
- mLights.add(getNextLight(false));
- createCameraPrivacyLightController();
+ prepareDefaultCameraPrivacyLightController(List.of(getNextLight(false)));
verify(mAppOpsManager, times(0)).startWatchingActive(any(), any(), any());
}
@Test
public void testAppsOpsListenerRegisteredWithCameraLight() {
- mLights.add(getNextLight(true));
-
- createCameraPrivacyLightController();
+ prepareDefaultCameraPrivacyLightController();
verify(mAppOpsManager, times(1)).startWatchingActive(any(), any(), any());
}
@@ -165,11 +198,12 @@
@Test
public void testAllCameraLightsAreRequestedOnOpActive() {
Random r = new Random(0);
+ List<Light> lights = new ArrayList<>();
for (int i = 0; i < 30; i++) {
- mLights.add(getNextLight(r.nextBoolean()));
+ lights.add(getNextLight(r.nextBoolean()));
}
- createCameraPrivacyLightController();
+ prepareDefaultCameraPrivacyLightController(lights);
// Verify no session has been opened at this point.
verify(mLightsManager, times(0)).openSession(anyInt());
@@ -181,8 +215,6 @@
verify(mLightsManager, times(1)).openSession(anyInt());
verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
- assertEquals("requestLights() not invoked exactly once",
- 1, mLightsRequestCaptor.getAllValues().size());
List<Integer> expectedCameraLightIds = mLights.stream()
.filter(l -> l.getType() == Light.LIGHT_TYPE_CAMERA)
@@ -199,40 +231,25 @@
@Test
public void testWillOnlyOpenOnceWhenTwoPackagesStartOp() {
- mLights.add(getNextLight(true));
-
- createCameraPrivacyLightController();
-
- verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
- AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
- listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
+ prepareDefaultCameraPrivacyLightController();
+ notifyCamOpChanged(10101, "pkg1", true);
verify(mLightsManager, times(1)).openSession(anyInt());
- listener.onOpActiveChanged(OPSTR_CAMERA, 10102, "pkg2", true);
+ notifyCamOpChanged(10102, "pkg2", true);
verify(mLightsManager, times(1)).openSession(anyInt());
}
@Test
public void testWillCloseOnFinishOp() {
- mLights.add(getNextLight(true));
-
- createCameraPrivacyLightController();
-
- verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
- AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
- listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
-
+ prepareDefaultCameraPrivacyLightController();
+ notifyCamOpChanged(10101, "pkg1", true);
verify(mLightsSession, times(0)).close();
- listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", false);
+ notifyCamOpChanged(10101, "pkg1", false);
verify(mLightsSession, times(1)).close();
}
@Test
public void testWillCloseOnFinishOpForAllPackages() {
- mLights.add(getNextLight(true));
-
- createCameraPrivacyLightController();
+ prepareDefaultCameraPrivacyLightController();
int numUids = 100;
List<Integer> uids = new ArrayList<>(numUids);
@@ -240,64 +257,52 @@
uids.add(10001 + i);
}
- verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
- AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
-
for (int i = 0; i < numUids; i++) {
- listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), true);
+ notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), true);
}
// Change the order which their ops are finished
Collections.shuffle(uids, new Random(0));
for (int i = 0; i < numUids - 1; i++) {
- listener.onOpActiveChanged(OPSTR_CAMERA, uids.get(i), "pkg" + (int) uids.get(i), false);
+ notifyCamOpChanged(uids.get(i), "pkg" + (int) uids.get(i), false);
}
verify(mLightsSession, times(0)).close();
int lastUid = uids.get(numUids - 1);
- listener.onOpActiveChanged(OPSTR_CAMERA, lastUid, "pkg" + lastUid, false);
+ notifyCamOpChanged(lastUid, "pkg" + lastUid, false);
verify(mLightsSession, times(1)).close();
}
@Test
public void testWontOpenForExemptedPackage() {
- mLights.add(getNextLight(true));
- mExemptedPackages.add("pkg1");
+ String exemptPackage = "pkg1";
+ prepareCameraPrivacyLightController(List.of(getNextLight(true)),
+ Set.of(exemptPackage), true, mDefaultColors, mDefaultAlsThresholdsLux,
+ mDefaultAlsAveragingIntervalMillis);
- createCameraPrivacyLightController();
-
- verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
-
- AppOpsManager.OnOpActiveChangedListener listener = mAppOpsListenerCaptor.getValue();
- listener.onOpActiveChanged(OPSTR_CAMERA, 10101, "pkg1", true);
+ notifyCamOpChanged(10101, exemptPackage, true);
verify(mLightsManager, times(0)).openSession(anyInt());
}
@Test
public void testNoLightSensor() {
- mLights.add(getNextLight(true));
- doReturn(null).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- createCameraPrivacyLightController();
+ prepareCameraPrivacyLightController(List.of(getNextLight(true)),
+ Set.of(), true, mDefaultColors, mDefaultAlsThresholdsLux,
+ mDefaultAlsAveragingIntervalMillis);
openCamera();
verify(mLightsSession).requestLights(mLightsRequestCaptor.capture());
LightsRequest lightsRequest = mLightsRequestCaptor.getValue();
for (LightState lightState : lightsRequest.getLightStates()) {
- assertEquals(mDayColor, lightState.getColor());
+ assertEquals(mDefaultColors[mDefaultColors.length - 1], lightState.getColor());
}
}
@Test
public void testALSListenerNotRegisteredUntilCameraIsOpened() {
- mLights.add(getNextLight(true));
- Sensor sensor = mock(Sensor.class);
- doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+ prepareDefaultCameraPrivacyLightController();
verify(mSensorManager, never()).registerListener(any(SensorEventListener.class),
any(Sensor.class), anyInt(), any(Handler.class));
@@ -307,113 +312,44 @@
verify(mSensorManager, times(1)).registerListener(mLightSensorListenerCaptor.capture(),
any(Sensor.class), anyInt(), any(Handler.class));
- mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", false);
+ notifyCamOpChanged(10001, "pkg", false);
verify(mSensorManager, times(1)).unregisterListener(mLightSensorListenerCaptor.getValue());
}
- @Ignore
@Test
- public void testDayColor() {
- testBrightnessToColor(20, mDayColor);
- }
-
- @Ignore
- @Test
- public void testNightColor() {
- testBrightnessToColor(10, mNightColor);
- }
-
- private void testBrightnessToColor(int brightnessValue, int color) {
- mLights.add(getNextLight(true));
- Sensor sensor = mock(Sensor.class);
- doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- CameraPrivacyLightController cplc = createCameraPrivacyLightController();
+ public void testAlsThresholds() {
+ CameraPrivacyLightController cplc = prepareDefaultCameraPrivacyLightController();
+ long elapsedTime = 0;
cplc.setElapsedRealTime(0);
-
openCamera();
+ for (int i = 0; i < mDefaultColors.length; i++) {
+ int expectedColor = mDefaultColors[i];
+ int alsLuxValue = i
+ == mDefaultAlsThresholdsLux.length
+ ? mDefaultAlsThresholdsLux[i - 1] : mDefaultAlsThresholdsLux[i] - 1;
- verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
- any(Sensor.class), anyInt(), any(Handler.class));
- SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
- float[] sensorEventValues = new float[1];
- SensorEvent sensorEvent = new SensorEvent(sensor, 0, 0, sensorEventValues);
+ notifySensorEvent(cplc, elapsedTime, alsLuxValue);
+ elapsedTime += mDefaultAlsAveragingIntervalMillis + 1;
+ notifySensorEvent(cplc, elapsedTime, alsLuxValue);
- sensorEventValues[0] = getLightSensorValue(brightnessValue);
- sensorListener.onSensorChanged(sensorEvent);
-
- verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
- for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
- assertEquals(color, lightState.getColor());
+ verify(mLightsSession, atLeastOnce()).requestLights(mLightsRequestCaptor.capture());
+ for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
+ assertEquals(expectedColor, lightState.getColor());
+ }
}
}
- @Ignore
- @Test
- public void testDayToNightTransistion() {
- mLights.add(getNextLight(true));
- Sensor sensor = mock(Sensor.class);
- doReturn(sensor).when(mSensorManager).getDefaultSensor(Sensor.TYPE_LIGHT);
-
- CameraPrivacyLightController cplc = createCameraPrivacyLightController();
- cplc.setElapsedRealTime(0);
-
- openCamera();
- // There will be an initial call at brightness 0
- verify(mLightsSession, times(1)).requestLights(any(LightsRequest.class));
-
- verify(mSensorManager).registerListener(mLightSensorListenerCaptor.capture(),
- any(Sensor.class), anyInt(), any(Handler.class));
- SensorEventListener sensorListener = mLightSensorListenerCaptor.getValue();
-
- onSensorEvent(cplc, sensorListener, sensor, 0, 20);
-
- // 5 sec avg = 20
- onSensorEvent(cplc, sensorListener, sensor, 5000, 30);
-
- verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
- for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
- assertEquals(mDayColor, lightState.getColor());
- }
-
- // 5 sec avg = 22
-
- onSensorEvent(cplc, sensorListener, sensor, 6000, 10);
-
- // 5 sec avg = 18
-
- onSensorEvent(cplc, sensorListener, sensor, 8000, 5);
-
- // Should have always been day
- verify(mLightsSession, times(2)).requestLights(mLightsRequestCaptor.capture());
- for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
- assertEquals(mDayColor, lightState.getColor());
- }
-
- // 5 sec avg = 12
-
- onSensorEvent(cplc, sensorListener, sensor, 10000, 5);
-
- // Should now be night
- verify(mLightsSession, times(3)).requestLights(mLightsRequestCaptor.capture());
- for (LightState lightState : mLightsRequestCaptor.getValue().getLightStates()) {
- assertEquals(mNightColor, lightState.getColor());
- }
+ private void notifyCamOpChanged(int uid, String pkg, boolean active) {
+ verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
+ mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, uid, pkg, active);
}
- private void onSensorEvent(CameraPrivacyLightController cplc,
- SensorEventListener sensorListener, Sensor sensor, long timestamp, int value) {
+ private void notifySensorEvent(CameraPrivacyLightController cplc, long timestamp, int value) {
cplc.setElapsedRealTime(timestamp);
- sensorListener.onSensorChanged(new SensorEvent(sensor, 0, timestamp,
- new float[] {getLightSensorValue(value)}));
- }
-
- // Use the test thread so that the test is deterministic
- private CameraPrivacyLightController createCameraPrivacyLightController() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
- return new CameraPrivacyLightController(mContext, Looper.myLooper());
+ verify(mSensorManager, atLeastOnce()).registerListener(mLightSensorListenerCaptor.capture(),
+ eq(mLightSensor), anyInt(), any());
+ mLightSensorListenerCaptor.getValue().onSensorChanged(new SensorEvent(mLightSensor, 0,
+ TimeUnit.MILLISECONDS.toNanos(timestamp), new float[] {value}));
}
private Light getNextLight(boolean cameraType) {
@@ -427,10 +363,6 @@
return light;
}
- private float getLightSensorValue(int i) {
- return (float) Math.exp(i / CameraPrivacyLightController.LIGHT_VALUE_MULTIPLIER);
- }
-
private void openCamera() {
verify(mAppOpsManager).startWatchingActive(any(), any(), mAppOpsListenerCaptor.capture());
mAppOpsListenerCaptor.getValue().onOpActiveChanged(OPSTR_CAMERA, 10001, "pkg", true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index b79c7be..349a597 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -303,6 +303,7 @@
});
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testStates_areMutuallyExclusive() {
forEachState(state1 -> {
@@ -523,6 +524,7 @@
});
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testTwoFingersOneTap_activatedState_dispatchMotionEvents() {
goFromStateIdleTo(STATE_ACTIVATED);
@@ -583,6 +585,7 @@
returnToNormalFrom(STATE_ACTIVATED);
}
+ @FlakyTest(bugId = 297879316)
@Test
public void testFirstFingerSwipe_twoPointerDownAndActivatedState_panningState() {
goFromStateIdleTo(STATE_ACTIVATED);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index bbbab21..a0bca3b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -50,6 +50,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.platform.test.annotations.FlakyTest;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.view.InputDevice;
@@ -307,6 +308,7 @@
MagnificationScaleProvider.MAX_SCALE);
}
+ @FlakyTest(bugId = 297879435)
@Test
public void logTrackingTypingFocus_processScroll_logDuration() {
WindowMagnificationManager spyWindowMagnificationManager = spy(mWindowMagnificationManager);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index aba24fb..7f8ad45 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -261,7 +261,8 @@
// Verify disconnection has been cancelled and we're seeing two connections attempts,
// with the device connected at the end of the test
verify(mSpyDevInventory, times(2)).onSetBtActiveDevice(
- any(AudioDeviceBroker.BtDeviceInfo.class), anyInt());
+ any(AudioDeviceBroker.BtDeviceInfo.class), anyInt() /*codec*/,
+ anyInt() /*streamType*/);
Assert.assertTrue("Mock device not connected",
mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
index 64e776e..a621c0c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -181,6 +181,7 @@
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@@ -203,6 +204,8 @@
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(500);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(400);
+ assertThat(authenticationStats.getEnrollmentNotifications())
+ .isEqualTo(MAXIMUM_ENROLLMENT_NOTIFICATIONS);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(0.8f);
}
@@ -230,6 +233,7 @@
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@@ -256,6 +260,7 @@
.getAuthenticationStatsForUser(USER_ID_1);
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
}
@@ -284,6 +289,8 @@
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ // Assert that notification count has been updated.
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1);
}
@Test
@@ -311,5 +318,7 @@
assertThat(authenticationStats.getTotalAttempts()).isEqualTo(0);
assertThat(authenticationStats.getRejectedAttempts()).isEqualTo(0);
assertThat(authenticationStats.getFrr()).isWithin(0f).of(-1.0f);
+ // Assert that notification count has been updated.
+ assertThat(authenticationStats.getEnrollmentNotifications()).isEqualTo(1);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
index dde2a3c..0c0d47a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsPersisterTest.java
@@ -217,6 +217,31 @@
}
@Test
+ public void persistFrrStats_multiUser_newUser_shouldUpdateRecord() throws JSONException {
+ AuthenticationStats authenticationStats1 = new AuthenticationStats(USER_ID_1,
+ 300 /* totalAttempts */, 10 /* rejectedAttempts */,
+ 0 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FACE);
+ AuthenticationStats authenticationStats2 = new AuthenticationStats(USER_ID_2,
+ 100 /* totalAttempts */, 5 /* rejectedAttempts */,
+ 1 /* enrollmentNotifications */, BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
+ // Sets up the shared preference with user 1 only.
+ when(mSharedPreferences.getStringSet(eq(KEY), anySet())).thenReturn(
+ Set.of(buildFrrStats(authenticationStats1)));
+
+ // Add data for user 2.
+ mAuthenticationStatsPersister.persistFrrStats(authenticationStats2.getUserId(),
+ authenticationStats2.getTotalAttempts(),
+ authenticationStats2.getRejectedAttempts(),
+ authenticationStats2.getEnrollmentNotifications(),
+ authenticationStats2.getModality());
+
+ verify(mEditor).putStringSet(eq(KEY), mStringSetArgumentCaptor.capture());
+ assertThat(mStringSetArgumentCaptor.getValue())
+ .contains(buildFrrStats(authenticationStats2));
+ }
+
+ @Test
public void removeFrrStats_existingUser_shouldUpdateRecord() throws JSONException {
AuthenticationStats authenticationStats = new AuthenticationStats(USER_ID_1,
300 /* totalAttempts */, 10 /* rejectedAttempts */,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 37f4983..70e5c2e1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -83,7 +83,6 @@
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -128,7 +127,6 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
@@ -596,9 +594,6 @@
mAcquiredWakeLocks.add(wl);
return wl;
});
- mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false);
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -1964,34 +1959,6 @@
}
@Test
- public void enqueueNotification_wakeLockSystemPropertyOff_noWakeLock() throws Exception {
- mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "true", false);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
- waitForIdle();
-
- verifyZeroInteractions(mPowerManager);
- }
-
- @Test
- public void enqueueNotification_wakeLockDeviceConfigOff_noWakeLock() throws Exception {
- mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.NOTIFY_WAKELOCK, "false", false);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- "enqueueNotification_setsWakeLockWorkSource", 0,
- generateNotificationRecord(null).getNotification(), 0);
- waitForIdle();
-
- verifyZeroInteractions(mPowerManager);
- }
-
- @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index e540068..e22c104 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -2470,6 +2470,143 @@
assertEquals(12345, mZenModeEventLogger.getPackageUid(4));
}
+ @Test
+ public void testUpdateConsolidatedPolicy_defaultRulesOnly() {
+ setupZenConfig();
+
+ // When there's one automatic rule active and it doesn't specify a policy, test that the
+ // resulting consolidated policy is one that matches the default rule settings.
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable the rule
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // inspect the consolidated policy. Based on setupZenConfig() values.
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowCalls());
+ assertEquals(PRIORITY_SENDERS_STARRED, mZenModeHelper.mConsolidatedPolicy.allowCallsFrom());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations());
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers());
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges());
+ }
+
+ @Test
+ public void testUpdateConsolidatedPolicy_customPolicyOnly() {
+ setupZenConfig();
+
+ // when there's only one automatic rule active and it has a custom policy, make sure that's
+ // what the consolidated policy reflects whether or not it's stricter than what the default
+ // would specify.
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowAlarms(true) // more lenient than default
+ .allowMedia(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showBadges(true) // more lenient
+ .showPeeking(false) // more restrictive
+ .build();
+
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable the rule; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // since this is the only active rule, the consolidated policy should match the custom
+ // policy for every field specified, and take default values for unspecified things
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // custom
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.showBadges()); // custom
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom
+ }
+
+ @Test
+ public void testUpdateConsolidatedPolicy_defaultAndCustomActive() {
+ setupZenConfig();
+
+ // when there are two rules active, one inheriting the default policy and one setting its
+ // own custom policy, they should be merged to form the most restrictive combination.
+
+ // rule 1: no custom policy
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ null,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test",
+ Process.SYSTEM_UID, true);
+
+ // enable rule 1
+ mZenModeHelper.setAutomaticZenRuleState(id,
+ new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // custom policy for rule 2
+ ZenPolicy customPolicy = new ZenPolicy.Builder()
+ .allowAlarms(true) // more lenient than default
+ .allowMedia(true) // more lenient than default
+ .allowRepeatCallers(false) // more restrictive than default
+ .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default
+ .showBadges(true) // more lenient
+ .showPeeking(false) // more restrictive
+ .build();
+
+ AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
+ null,
+ new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ customPolicy,
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2,
+ "test", Process.SYSTEM_UID, true);
+
+ // enable rule 2; this will update the consolidated policy
+ mZenModeHelper.setAutomaticZenRuleState(id2,
+ new Condition(zenRule2.getConditionId(), "", STATE_TRUE),
+ Process.SYSTEM_UID, true);
+
+ // now both rules should be on, and the consolidated policy should reflect the most
+ // restrictive option of each of the two
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default, unset in custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowReminders()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowCalls()); // custom stricter
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMessages()); // default, unset in custom
+ assertTrue(mZenModeHelper.mConsolidatedPolicy.allowConversations()); // default
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()); // custom stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showBadges()); // default stricter
+ assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom stricter
+ }
+
private void setupZenConfig() {
mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
mZenModeHelper.mConfig.allowAlarms = false;
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 44cf333..085241f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -63,7 +63,6 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import com.android.server.LocalServices;
@@ -1005,7 +1004,6 @@
mVibratorProviders.get(3).getEffectSegments(vibrationId));
}
- @FlakyTest
@Test
public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
int[] vibratorIds = new int[]{1, 2};
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 302ad7f..31682bc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1541,7 +1541,8 @@
// Make keyguard locked and set the top activity show-when-locked.
KeyguardController keyguardController = activity.mTaskSupervisor.getKeyguardController();
int displayId = activity.getDisplayId();
- doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
+ keyguardController.setKeyguardShown(displayId, true /* keyguardShowing */,
+ false /* aodShowing */);
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
@@ -1553,7 +1554,7 @@
// Verify the stack-top activity is occluded keyguard.
assertEquals(topActivity, task.topRunningActivity());
- assertTrue(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
+ assertTrue(keyguardController.isKeyguardOccluded(displayId));
// Finish the top activity
topActivity.setState(PAUSED, "true");
@@ -1562,7 +1563,7 @@
// Verify new top activity does not occlude keyguard.
assertEquals(activity, task.topRunningActivity());
- assertFalse(keyguardController.isDisplayOccluded(DEFAULT_DISPLAY));
+ assertFalse(keyguardController.isKeyguardOccluded(displayId));
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index d169a58..5341588 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -24,7 +24,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
@@ -41,11 +40,9 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,7 +57,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.IBinder;
import android.os.LocaleList;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -76,7 +72,6 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
-import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.List;
@@ -304,18 +299,11 @@
*/
@Test
public void testEnterPipModeWhenRecordParentChangesToNull() {
- MockitoSession mockSession = mockitoSession()
- .initMocks(this)
- .mockStatic(ActivityRecord.class)
- .startMocking();
-
- ActivityRecord record = mock(ActivityRecord.class);
- IBinder token = mock(IBinder.class);
+ final ActivityRecord record = new ActivityBuilder(mAtm).setCreateTask(true).build();
PictureInPictureParams params = mock(PictureInPictureParams.class);
record.pictureInPictureArgs = params;
//mock operations in private method ensureValidPictureInPictureActivityParamsLocked()
- when(ActivityRecord.forTokenLocked(token)).thenReturn(record);
doReturn(true).when(record).supportsPictureInPicture();
doReturn(false).when(params).hasSetAspectRatio();
@@ -323,15 +311,13 @@
doReturn(true).when(record)
.checkEnterPictureInPictureState("enterPictureInPictureMode", false);
doReturn(false).when(record).inPinnedWindowingMode();
- doReturn(false).when(mAtm).isKeyguardLocked(anyInt());
+ doReturn(false).when(record).isKeyguardLocked();
//to simulate NPE
doReturn(null).when(record).getParent();
- mAtm.mActivityClientController.enterPictureInPictureMode(token, params);
+ mAtm.mActivityClientController.enterPictureInPictureMode(record.token, params);
//if record's null parent is not handled gracefully, test will fail with NPE
-
- mockSession.finishMocking();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index a2b7da3..ae4ebc1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1698,14 +1698,17 @@
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
- doReturn(true).when(app).isInTransition();
+ doReturn(true).when(app).inTransitionSelfOrParent();
// If the task contains a transition, this should be no-op.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
assertTrue(app2.hasFixedRotationTransform());
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
- doReturn(false).when(app).isInTransition();
+ // The display should be unlikely to be in transition, but if it happens, the fixed
+ // rotation should proceed to finish because the activity/task level transition is finished.
+ doReturn(true).when(mDisplayContent).inTransition();
+ doReturn(false).when(app).inTransitionSelfOrParent();
// Although this notifies app instead of app2 that uses the fixed rotation, app2 should
// still finish the transform because there is no more transition event.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index c3db241..4165911 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -26,7 +26,6 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -35,9 +34,14 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.WindowConfiguration;
+import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
@@ -50,6 +54,8 @@
import android.window.WindowContextInfo;
import android.window.WindowTokenClient;
+import androidx.annotation.NonNull;
+
import com.android.server.inputmethod.InputMethodDialogWindowContext;
import org.junit.After;
@@ -58,6 +64,9 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
// TODO(b/157888351): Move the test to inputmethod package once we find the way to test the
// scenario there.
/**
@@ -138,44 +147,96 @@
@Test
public void testGetSettingsContextOnDualDisplayContent() {
final Context context = mWindowContext.get(mSecondaryDisplay.getDisplayId());
+ final MaxBoundsVerifier maxBoundsVerifier = new MaxBoundsVerifier();
+ context.registerComponentCallbacks(maxBoundsVerifier);
+
final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken();
- assertNotNull(tokenClient);
- spyOn(tokenClient);
+ spyOn(requireNonNull(tokenClient));
final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer();
spyOn(imeContainer);
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay);
- mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup firstDaGroup = mSecondaryDisplay.mFirstRoot;
+ maxBoundsVerifier.setMaxBounds(firstDaGroup.getMaxBounds());
+
+ firstDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()));
+ eq(firstDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mFirstRoot.getConfiguration()),
+ eq(firstDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(firstDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
// Clear the previous invocation histories in case we may count the previous
// onConfigurationChanged invocation into the next verification.
clearInvocations(tokenClient, imeContainer);
- mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer);
+ final DisplayAreaGroup secondDaGroup = mSecondaryDisplay.mSecondRoot;
+ maxBoundsVerifier.setMaxBounds(secondDaGroup.getMaxBounds());
+
+ secondDaGroup.placeImeContainer(imeContainer);
verify(imeContainer, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()));
+ eq(secondDaGroup.getConfiguration()));
verify(tokenClient, timeout(WAIT_TIMEOUT_MS)).onConfigurationChanged(
- eq(mSecondaryDisplay.mSecondRoot.getConfiguration()),
+ eq(secondDaGroup.getConfiguration()),
eq(mSecondaryDisplay.mDisplayId));
- assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot);
+ assertThat(imeContainer.getRootDisplayArea()).isEqualTo(secondDaGroup);
+ maxBoundsVerifier.waitAndAssertMaxMetricsMatches();
assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay);
}
private void assertImeSwitchContextMetricsValidity(Context context, DisplayContent dc) {
assertThat(context.getDisplayId()).isEqualTo(dc.getDisplayId());
+ final Rect imeContainerBounds = dc.getImeContainer().getBounds();
final Rect contextBounds = context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics().getBounds();
- final Rect imeContainerBounds = dc.getImeContainer().getBounds();
assertThat(contextBounds).isEqualTo(imeContainerBounds);
}
+
+ private static final class MaxBoundsVerifier implements ComponentCallbacks {
+
+ private CountDownLatch mLatch;
+
+ private Rect mMaxBounds;
+
+ /**
+ * Sets max bounds to verify whether it matches the
+ * {@link WindowConfiguration#getMaxBounds()} reported from
+ * {@link #onConfigurationChanged(Configuration)} callback, and also resets the count down
+ * latch.
+ *
+ * @param maxBounds max bounds to verify
+ */
+ private void setMaxBounds(@NonNull Rect maxBounds) {
+ mMaxBounds = maxBounds;
+ mLatch = new CountDownLatch(1);
+ }
+
+ /**
+ * Waits for the {@link #onConfigurationChanged(Configuration)} callback whose the reported
+ * {@link WindowConfiguration#getMaxBounds()} matches {@link #mMaxBounds}.
+ */
+ private void waitAndAssertMaxMetricsMatches() {
+ try {
+ assertThat(mLatch.await(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Test failed because of " + e);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ if (newConfig.windowConfiguration.getMaxBounds().equals(mMaxBounds)) {
+ mLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onLowMemory() {}
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 13e5ff1..0bb75d8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1999,7 +1999,7 @@
"lte_plus_threshold_bandwidth_khz_int";
/**
- * The combined channel bandwidth threshold (non-inclusive) in KHz required to display the
+ * The combined channel bandwidth threshold (inclusive) in KHz required to display the
* NR advanced (i.e. 5G+) data icon. It is 0 by default, meaning minimum bandwidth check is
* not enabled. Other factors like bands or frequency can also determine whether the NR
* advanced data icon is shown or not.
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index b94d14f..0fc2617 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1540,7 +1540,7 @@
}
const String8& featureOfBase = bundle->getFeatureOfPackage();
- if (!featureOfBase.isEmpty()) {
+ if (!featureOfBase.empty()) {
if (bundle->getVerbose()) {
printf("Including base feature resources from package: %s\n",
featureOfBase.c_str());
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 5a06b10..60f3f27 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -1133,7 +1133,7 @@
if (code == ResXMLTree::END_TAG) {
depth--;
if (depth < 2) {
- if (withinSupportsInput && !supportedInput.isEmpty()) {
+ if (withinSupportsInput && !supportedInput.empty()) {
printf("supports-input: '");
const size_t N = supportedInput.size();
for (size_t i=0; i<N; i++) {
@@ -1300,7 +1300,7 @@
ResTable::normalizeForOutput(versionName.c_str()).c_str());
String8 splitName = AaptXml::getAttribute(tree, NULL, "split");
- if (!splitName.isEmpty()) {
+ if (!splitName.empty()) {
printf(" split='%s'", ResTable::normalizeForOutput(
splitName.c_str()).c_str());
}
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c944e0..4a360ed 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1285,7 +1285,7 @@
packageType = ResourceTable::SharedLibrary;
} else if (bundle->getExtending()) {
packageType = ResourceTable::System;
- } else if (!bundle->getFeatureOfPackage().isEmpty()) {
+ } else if (!bundle->getFeatureOfPackage().empty()) {
packageType = ResourceTable::AppFeature;
}
@@ -3144,7 +3144,7 @@
tree.restart();
- if (!startTags.isEmpty()) {
+ if (!startTags.empty()) {
bool haveStart = false;
while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
if (code != ResXMLTree::START_TAG) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 449e080..bc252cf 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1813,7 +1813,7 @@
mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage);
const String8& featureAfter = bundle->getFeatureAfterPackage();
- if (!featureAfter.isEmpty()) {
+ if (!featureAfter.empty()) {
AssetManager featureAssetManager;
if (!featureAssetManager.addAssetPath(featureAfter, NULL)) {
fprintf(stderr, "ERROR: Feature package '%s' not found.\n",
@@ -1823,7 +1823,7 @@
const ResTable& featureTable = featureAssetManager.getResources(false);
mTypeIdOffset = std::max(mTypeIdOffset,
- findLargestTypeIdForPackage(featureTable, mAssetsPackage));
+ findLargestTypeIdForPackage(featureTable, mAssetsPackage));
}
return NO_ERROR;
@@ -3252,7 +3252,7 @@
// If we're building splits, then each invocation of the flattening
// step will have 'missing' entries. Don't warn/error for this case.
- if (bundle->getSplitConfigurations().isEmpty()) {
+ if (bundle->getSplitConfigurations().empty()) {
bool missing_entry = false;
const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ?
"error" : "warning";
@@ -4858,7 +4858,7 @@
Vector<sp<XMLNode> > nodesToVisit;
nodesToVisit.push(root);
- while (!nodesToVisit.isEmpty()) {
+ while (!nodesToVisit.empty()) {
sp<XMLNode> node = nodesToVisit.top();
nodesToVisit.pop();
diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp
index e130286..354a65c 100644
--- a/tools/aapt/SourcePos.cpp
+++ b/tools/aapt/SourcePos.cpp
@@ -78,7 +78,7 @@
break;
}
- if (!this->file.isEmpty()) {
+ if (!this->file.empty()) {
if (this->line >= 0) {
fprintf(to, "%s:%d: %s%s\n", this->file.c_str(), this->line, type, this->error.c_str());
} else {
diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp
index 52949da..a0679a6 100644
--- a/tools/aapt2/DominatorTree_test.cpp
+++ b/tools/aapt2/DominatorTree_test.cpp
@@ -50,8 +50,7 @@
private:
void VisitConfig(const DominatorTree::Node* node, const int indent) {
auto config_string = node->value()->config.toString();
- buffer_ << std::string(indent, ' ')
- << (config_string.isEmpty() ? "<default>" : config_string)
+ buffer_ << std::string(indent, ' ') << (config_string.empty() ? "<default>" : config_string)
<< std::endl;
}
diff --git a/tools/obbtool/Main.cpp b/tools/obbtool/Main.cpp
index 64808c0..7014068 100644
--- a/tools/obbtool/Main.cpp
+++ b/tools/obbtool/Main.cpp
@@ -135,7 +135,7 @@
}
printf("OBB info for '%s':\n", filename);
- printf("Package name: %s\n", obb->getPackageName().string());
+ printf("Package name: %s\n", obb->getPackageName().c_str());
printf(" Version: %d\n", obb->getVersion());
printf(" Flags: 0x%08x\n", obb->getFlags());
printf(" Overlay: %s\n", obb->isOverlay() ? "true" : "false");
diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp
index 4e2b48e..7150008 100644
--- a/tools/split-select/SplitDescription.cpp
+++ b/tools/split-select/SplitDescription.cpp
@@ -70,7 +70,7 @@
String8 SplitDescription::toString() const {
String8 extension;
if (abi != abi::Variant_none) {
- if (extension.isEmpty()) {
+ if (extension.empty()) {
extension.append(":");
} else {
extension.append("-");