Merge "Add shell command to get aconfig flag values." into main
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 55ec7da..6e51f00 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -86,6 +86,7 @@
static_libs: [
"libidmap2_policies",
"libidmap2_protos",
+ "libpng",
],
shared_libs: [
"libandroidfw",
@@ -107,6 +108,7 @@
"libcutils",
"libidmap2_policies",
"libidmap2_protos",
+ "libpng",
"libprotobuf-cpp-lite",
"libutils",
"libz",
@@ -185,6 +187,7 @@
static_libs: [
"libgmock",
"libidmap2_protos",
+ "libpng",
],
target: {
android: {
@@ -258,6 +261,7 @@
"libbase",
"libcutils",
"libidmap2",
+ "libpng",
"libprotobuf-cpp-lite",
"libutils",
"libz",
@@ -275,6 +279,7 @@
"libidmap2",
"libidmap2_policies",
"liblog",
+ "libpng",
"libprotobuf-cpp-lite",
"libutils",
"libziparchive",
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index d76ca5b..f264125 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -266,7 +266,8 @@
} else if (res.binaryData.has_value()) {
builder.SetResourceValue(res.resourceName, res.binaryData->get(),
res.binaryDataOffset, res.binaryDataSize,
- res.configuration.value_or(std::string()));
+ res.configuration.value_or(std::string()),
+ res.isNinePatch);
} else {
builder.SetResourceValue(res.resourceName, res.dataType, res.data,
res.configuration.value_or(std::string()));
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 8ebd454..bca2ff3 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -28,4 +28,5 @@
@nullable @utf8InCpp String configuration;
long binaryDataOffset;
long binaryDataSize;
+ boolean isNinePatch;
}
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 1e7d4c2..bfcd4b9 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -19,6 +19,8 @@
#include <libidmap2/proto/fabricated_v1.pb.h>
+#include "androidfw/Streams.h"
+
#include <istream>
#include <map>
#include <memory>
@@ -51,7 +53,8 @@
std::optional<android::base::borrowed_fd>&& binary_value,
off64_t data_binary_offset,
size_t data_binary_size,
- const std::string& configuration);
+ const std::string& configuration,
+ bool nine_patch);
inline Builder& setFrroPath(std::string frro_path) {
frro_path_ = std::move(frro_path);
@@ -70,6 +73,7 @@
off64_t data_binary_offset;
size_t data_binary_size;
std::string configuration;
+ bool nine_patch;
};
std::string package_name_;
@@ -81,7 +85,7 @@
};
struct BinaryData {
- android::base::borrowed_fd file_descriptor;
+ std::unique_ptr<android::InputStream> input_stream;
off64_t offset;
size_t size;
};
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index d4490ef4..9e463c9 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -45,6 +45,7 @@
std::optional<android::base::borrowed_fd> data_binary_value;
off64_t data_binary_offset;
size_t data_binary_size;
+ bool nine_patch;
};
struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 47daf23..16bb896 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -20,8 +20,16 @@
#include <sys/types.h> // umask
#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <androidfw/BigBuffer.h>
+#include <androidfw/BigBufferStream.h>
+#include <androidfw/FileStream.h>
+#include <androidfw/Image.h>
+#include <androidfw/Png.h>
#include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPiece.h>
#include <androidfw/StringPool.h>
+#include <androidfw/Streams.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <utils/ByteOrder.h>
@@ -32,9 +40,9 @@
#include <memory>
#include <string>
#include <utility>
+#include <sys/utsname.h>
namespace android::idmap2 {
-
constexpr auto kBufferSize = 1024;
namespace {
@@ -81,7 +89,7 @@
const std::string& resource_name, uint8_t data_type, uint32_t data_value,
const std::string& configuration) {
entries_.emplace_back(
- Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration});
+ Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration, false});
return *this;
}
@@ -89,18 +97,90 @@
const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
const std::string& configuration) {
entries_.emplace_back(
- Entry{resource_name, data_type, 0, data_string_value, std::nullopt, 0, 0, configuration});
+ Entry{resource_name,
+ data_type,
+ 0,
+ data_string_value,
+ std::nullopt,
+ 0,
+ 0,
+ configuration,
+ false});
return *this;
}
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
- off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration) {
+ off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration,
+ bool nine_patch) {
entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value,
- data_binary_offset, data_binary_size, configuration});
+ data_binary_offset, data_binary_size, configuration, nine_patch});
return *this;
}
+static Result<FabricatedOverlay::BinaryData> buildBinaryData(
+ pb::ResourceValue* pb_value, const TargetValue &value) {
+ pb_value->set_data_type(Res_value::TYPE_STRING);
+ size_t binary_size;
+ off64_t binary_offset;
+ std::unique_ptr<android::InputStream> binary_stream;
+
+ if (value.nine_patch) {
+ std::string file_contents;
+ file_contents.resize(value.data_binary_size);
+ if (!base::ReadFullyAtOffset(value.data_binary_value->get(), file_contents.data(),
+ value.data_binary_size, value.data_binary_offset)) {
+ return Error("Failed to read binary file data.");
+ }
+ const StringPiece content(file_contents.c_str(), file_contents.size());
+ android::PngChunkFilter png_chunk_filter(content);
+ android::AndroidLogDiagnostics diag;
+ auto png = android::ReadPng(&png_chunk_filter, &diag);
+ if (!png) {
+ return Error("Error opening file as png");
+ }
+
+ std::string err;
+ std::unique_ptr<NinePatch> nine_patch = NinePatch::Create(png->rows.get(),
+ png->width, png->height,
+ &err);
+ if (!nine_patch) {
+ return Error("%s", err.c_str());
+ }
+
+ png->width -= 2;
+ png->height -= 2;
+ memmove(png->rows.get(), png->rows.get() + 1, png->height * sizeof(uint8_t**));
+ for (int32_t h = 0; h < png->height; h++) {
+ memmove(png->rows[h], png->rows[h] + 4, png->width * 4);
+ }
+
+ android::BigBuffer buffer(value.data_binary_size);
+ android::BigBufferOutputStream buffer_output_stream(&buffer);
+ if (!android::WritePng(png.get(), nine_patch.get(), &buffer_output_stream, {},
+ &diag, false)) {
+ return Error("Error writing frro png");
+ }
+
+ binary_size = buffer.size();
+ binary_offset = 0;
+ android::BigBufferInputStream *buffer_input_stream
+ = new android::BigBufferInputStream(std::move(buffer));
+ binary_stream.reset(buffer_input_stream);
+ } else {
+ binary_size = value.data_binary_size;
+ binary_offset = value.data_binary_offset;
+ android::FileInputStream *fis
+ = new android::FileInputStream(value.data_binary_value.value());
+ binary_stream.reset(fis);
+ }
+
+ return FabricatedOverlay::BinaryData{
+ std::move(binary_stream),
+ binary_offset,
+ binary_size};
+}
+
Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
@@ -150,7 +230,8 @@
value->second = TargetValue{res_entry.data_type, res_entry.data_value,
res_entry.data_string_value, res_entry.data_binary_value,
- res_entry.data_binary_offset, res_entry.data_binary_size};
+ res_entry.data_binary_offset, res_entry.data_binary_size,
+ res_entry.nine_patch};
}
pb::FabricatedOverlay overlay_pb;
@@ -183,18 +264,20 @@
auto ref = string_pool.MakeRef(value.second.data_string_value);
pb_value->set_data_value(ref.index());
} else if (value.second.data_binary_value.has_value()) {
- pb_value->set_data_type(Res_value::TYPE_STRING);
- std::string uri
- = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
- static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
- static_cast<int> (value.second.data_binary_size));
- total_binary_bytes += value.second.data_binary_size;
- binary_files.emplace_back(FabricatedOverlay::BinaryData{
- value.second.data_binary_value->get(),
- value.second.data_binary_offset,
- value.second.data_binary_size});
- auto ref = string_pool.MakeRef(std::move(uri));
- pb_value->set_data_value(ref.index());
+ auto binary_data = buildBinaryData(pb_value, value.second);
+ if (!binary_data) {
+ return binary_data.GetError();
+ }
+ pb_value->set_data_type(Res_value::TYPE_STRING);
+
+ std::string uri
+ = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
+ static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
+ static_cast<int> (binary_data->size));
+ total_binary_bytes += binary_data->size;
+ binary_files.emplace_back(std::move(*binary_data));
+ auto ref = string_pool.MakeRef(std::move(uri));
+ pb_value->set_data_value(ref.index());
} else {
pb_value->set_data_value(value.second.data_value);
}
@@ -311,9 +394,9 @@
Write32(stream, (*data)->pb_crc);
Write32(stream, total_binary_bytes_);
std::string file_contents;
- for (const FabricatedOverlay::BinaryData fd : binary_files_) {
- file_contents.resize(fd.size);
- if (!ReadFullyAtOffset(fd.file_descriptor, file_contents.data(), fd.size, fd.offset)) {
+ for (const FabricatedOverlay::BinaryData& bd : binary_files_) {
+ file_contents.resize(bd.size);
+ if (!bd.input_stream->ReadFullyAtOffset(file_contents.data(), bd.size, bd.offset)) {
return Error("Failed to read binary file data.");
}
stream.write(file_contents.data(), file_contents.length());
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index c7f5cf3..7f9c468 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -53,7 +53,7 @@
if (entry_params.data_binary_value.has_value()) {
builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
entry_params.binary_data_offset, entry_params.binary_data_size,
- entry_params.configuration);
+ entry_params.configuration, entry_params.nine_patch);
} else if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
builder.SetResourceValue(entry_params.resource_name, dataType,
entry_params.data_value, entry_params.configuration);
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index b460bb3..6b1c7e8 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -59,7 +59,7 @@
Res_value::TYPE_STRING,
"foobar",
"en-rUS-normal-xxhdpi-v21")
- .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
+ .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
.setFrroPath("/foo/bar/biz.frro")
.Build();
ASSERT_TRUE(overlay);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index a3448fd..a384305 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -269,7 +269,7 @@
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
- .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
+ .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7", false)
.setFrroPath("/foo/bar/biz.frro")
.Build();
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 40f98c2..db44c23 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -212,7 +212,7 @@
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
.SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
- .SetResourceValue("drawable/dr1", fd, 0, 8341, "")
+ .SetResourceValue("drawable/dr1", fd, 0, 8341, "", false)
.setFrroPath("/foo/bar/biz.frro")
.Build();
diff --git a/core/api/current.txt b/core/api/current.txt
index adf3779..7731fac 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -11787,6 +11787,7 @@
public class FabricatedOverlay {
ctor public FabricatedOverlay(@NonNull String, @NonNull String);
method @NonNull public android.content.om.OverlayIdentifier getIdentifier();
+ method @FlaggedApi("android.content.res.nine_patch_frro") @NonNull public void setNinePatchResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 71a05a9..076fddf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1597,6 +1597,10 @@
method public void restoreDozeSettings(int);
}
+ public final class ColorDisplayManager {
+ method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public boolean isSaturationActivated();
+ }
+
public final class DisplayManager {
method public boolean areUserDisabledHdrTypesAllowed();
method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearGlobalUserPreferredDisplayMode();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 1000612..2a7dbab 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -566,8 +566,10 @@
public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
/**
- * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
- * play/stop media
+ * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer and hang up calls
+ * and play and stop media. Calling takes priority. If there is an incoming call,
+ * this action can be used to answer that call, and if there is an ongoing call, to hang up on
+ * that call.
*/
public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8af1216..adaaee2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2292,7 +2292,8 @@
case DUMP_HEAP: return "DUMP_HEAP";
case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
- case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
+ case UPDATE_PACKAGE_COMPATIBILITY_INFO:
+ return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
case DUMP_PROVIDER: return "DUMP_PROVIDER";
case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
@@ -3804,7 +3805,7 @@
boolean isSandboxActivityContext =
sandboxActivitySdkBasedContext()
- && SdkSandboxActivityAuthority.isSdkSandboxActivity(
+ && SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(
mSystemContext, r.intent);
boolean isSandboxedSdkContextUsed = false;
ContextImpl activityBaseContext;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index d660078..820ff3e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -21,6 +21,8 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static com.android.window.flags.Flags.multiCrop;
+
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -857,8 +859,7 @@
*/
public static boolean isMultiCropEnabled() {
if (sGlobals == null) {
- sIsMultiCropEnabled = SystemProperties.getBoolean(
- "persist.wm.debug.wallpaper_multi_crop", false);
+ sIsMultiCropEnabled = multiCrop();
}
if (sIsMultiCropEnabled == null) {
try {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index fc3a906..1e538c5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9146,7 +9146,7 @@
@UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public boolean isDeviceOwnerApp(String packageName) {
throwIfParentInstance("isDeviceOwnerApp");
- if (android.permission.flags.Flags.roleControllerInSystemServer()
+ if (android.permission.flags.Flags.systemServerRoleControllerEnabled()
&& CompatChanges.isChangeEnabled(IS_DEVICE_OWNER_USER_AWARE)) {
return isDeviceOwnerAppOnContextUser(packageName);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 38bcfa2..5c6c49a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12606,8 +12606,11 @@
}
/**
- * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivity} instead.
+ * @deprecated Use {@link SdkSandboxActivityAuthority#isSdkSandboxActivityIntent} instead.
* Once the other API is finalized this method will be removed.
+ *
+ * TODO(b/300059435): remove as part of the cleanup.
+ *
* @hide
*/
@Deprecated
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index df2d7e7..40ffb0f 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -281,8 +281,8 @@
@NonNull ParcelFileDescriptor value,
@Nullable String configuration) {
ensureValidResourceName(resourceName);
- mEntries.add(
- generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+ mEntries.add(generateFabricatedOverlayInternalEntry(
+ resourceName, value, configuration, false));
return this;
}
@@ -361,6 +361,16 @@
}
/**
+ * Set the package that owns the overlay
+ *
+ * @param owningPackage the package that should own the overlay.
+ * @hide
+ */
+ public void setOwningPackage(@NonNull String owningPackage) {
+ mOverlay.packageName = owningPackage;
+ }
+
+ /**
* Set the target overlayable name of the overlay
*
* The target package defines may define several overlayables. The {@link FabricatedOverlay}
@@ -442,13 +452,14 @@
@NonNull
private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
@NonNull String resourceName, @NonNull ParcelFileDescriptor parcelFileDescriptor,
- @Nullable String configuration) {
+ @Nullable String configuration, boolean isNinePatch) {
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.binaryData = Objects.requireNonNull(parcelFileDescriptor);
entry.configuration = configuration;
entry.binaryDataOffset = 0;
entry.binaryDataSize = parcelFileDescriptor.getStatSize();
+ entry.isNinePatch = isNinePatch;
return entry;
}
@@ -534,7 +545,26 @@
@Nullable String configuration) {
ensureValidResourceName(resourceName);
mOverlay.entries.add(
- generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+ generateFabricatedOverlayInternalEntry(resourceName, value, configuration, false));
+ }
+
+ /**
+ * Sets the resource value in the fabricated overlay from a nine patch.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param value the file descriptor whose contents are the value of the frro
+ * @param configuration The string representation of the config this overlay is enabled for
+ */
+ @NonNull
+ @FlaggedApi(android.content.res.Flags.FLAG_NINE_PATCH_FRRO)
+ public void setNinePatchResourceValue(
+ @NonNull String resourceName,
+ @NonNull ParcelFileDescriptor value,
+ @Nullable String configuration) {
+ ensureValidResourceName(resourceName);
+ mOverlay.entries.add(
+ generateFabricatedOverlayInternalEntry(resourceName, value, configuration, true));
}
/**
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 323592c..d13d962 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -55,6 +55,7 @@
* from the AndroidManifest.xml's <activity> and
* <receiver> tags.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ActivityInfo extends ComponentInfo implements Parcelable {
private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index f0b99f1..16a80e9 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -66,6 +66,7 @@
* corresponds to information collected from the AndroidManifest.xml's
* <application> tag.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ApplicationInfo extends PackageItemInfo implements Parcelable {
private static final ForBoolean sForBoolean = Parcelling.Cache.getOrCreate(ForBoolean.class);
private static final Parcelling.BuiltIn.ForStringSet sForStringSet =
@@ -1386,6 +1387,7 @@
*
* @see #category
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public static CharSequence getCategoryTitle(Context context, @Category int category) {
switch (category) {
case ApplicationInfo.CATEGORY_GAME:
@@ -2187,6 +2189,7 @@
* @return Returns a CharSequence containing the application's description.
* If there is no description, null is returned.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public CharSequence loadDescription(PackageManager pm) {
if (descriptionRes != 0) {
CharSequence label = pm.getText(packageName, descriptionRes, this);
@@ -2222,6 +2225,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = Environment.class)
public void initForUser(int userId) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
@@ -2414,6 +2418,7 @@
* @hide
*/
@Override
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadDefaultIcon(PackageManager pm) {
if ((flags & FLAG_EXTERNAL_STORAGE) != 0
&& isPackageUnavailable(pm)) {
@@ -2424,6 +2429,7 @@
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = PackageManager.class)
private boolean isPackageUnavailable(PackageManager pm) {
try {
return pm.getPackageInfo(packageName, 0) == null;
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 42847c8..ff48ffa 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -37,6 +37,7 @@
* implement Parcelable, but does provide convenience methods to assist
* in the implementation of Parcelable in subclasses.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ComponentInfo extends PackageItemInfo {
/**
* Global information about the application/package this component is a
@@ -258,6 +259,7 @@
* @hide
*/
@Override
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadDefaultIcon(PackageManager pm) {
return applicationInfo.loadIcon(pm);
}
@@ -265,6 +267,7 @@
/**
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
@Override protected Drawable loadDefaultBanner(PackageManager pm) {
return applicationInfo.loadBanner(pm);
}
@@ -273,6 +276,7 @@
* @hide
*/
@Override
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
protected Drawable loadDefaultLogo(PackageManager pm) {
return applicationInfo.loadLogo(pm);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5736a6d..5dee65b 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -29,6 +29,7 @@
* Overall information about the contents of a package. This corresponds
* to all of the information collected from AndroidManifest.xml.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PackageInfo implements Parcelable {
/**
* The name of this package. From the <manifest> tag's "name"
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 70e6f98..1f821b9 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -49,6 +49,7 @@
* itself implement Parcelable, but does provide convenience methods to assist
* in the implementation of Parcelable in subclasses.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PackageItemInfo {
/**
@@ -214,6 +215,7 @@
* @return Returns a CharSequence containing the item's label. If the
* item does not have a label, its name is returned.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
if (sForceSafeLabels && !Objects.equals(packageName, ActivityThread.currentPackageName())) {
return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
@@ -226,6 +228,7 @@
}
/** {@hide} */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public CharSequence loadUnsafeLabel(PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
@@ -248,6 +251,7 @@
*/
@SystemApi
@Deprecated
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm) {
return loadSafeLabel(pm, DEFAULT_MAX_LABEL_SIZE_PX, SAFE_STRING_FLAG_TRIM
| SAFE_STRING_FLAG_FIRST_LINE);
@@ -261,6 +265,7 @@
* @hide
*/
@SystemApi
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public @NonNull CharSequence loadSafeLabel(@NonNull PackageManager pm,
@FloatRange(from = 0) float ellipsizeDip, @TextUtils.SafeStringFlags int flags) {
Objects.requireNonNull(pm);
@@ -281,6 +286,7 @@
* item does not have an icon, the item's default icon is returned
* such as the default activity icon.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadIcon(PackageManager pm) {
return pm.loadItemIcon(this, getApplicationInfo());
}
@@ -298,6 +304,7 @@
* item does not have an icon, the item's default icon is returned
* such as the default activity icon.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadUnbadgedIcon(PackageManager pm) {
return pm.loadUnbadgedItemIcon(this, getApplicationInfo());
}
@@ -313,6 +320,7 @@
* @return Returns a Drawable containing the item's banner. If the item
* does not have a banner, this method will return null.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadBanner(PackageManager pm) {
if (banner != 0) {
Drawable dr = pm.getDrawable(packageName, banner, getApplicationInfo());
@@ -334,6 +342,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadDefaultIcon(PackageManager pm) {
return pm.getDefaultActivityIcon();
}
@@ -349,6 +358,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
protected Drawable loadDefaultBanner(PackageManager pm) {
return null;
}
@@ -364,6 +374,7 @@
* @return Returns a Drawable containing the item's logo. If the item
* does not have a logo, this method will return null.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadLogo(PackageManager pm) {
if (logo != 0) {
Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo());
@@ -385,6 +396,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
protected Drawable loadDefaultLogo(PackageManager pm) {
return null;
}
@@ -402,6 +414,7 @@
* assigned as the given meta-data. If the meta-data name is not defined
* or the XML resource could not be found, null is returned.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public XmlResourceParser loadXmlMetaData(PackageManager pm, String name) {
if (metaData != null) {
int resid = metaData.getInt(name);
diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java
index 11c9a7d..743ff9a 100644
--- a/core/java/android/content/pm/PathPermission.java
+++ b/core/java/android/content/pm/PathPermission.java
@@ -24,6 +24,7 @@
* Description of permissions needed to access a particular path
* in a {@link ProviderInfo}.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PathPermission extends PatternMatcher {
private final String mReadPermission;
private final String mWritePermission;
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 3984ade..9e553db 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -27,6 +27,7 @@
* {@link android.content.pm.PackageManager#resolveContentProvider(java.lang.String, int)
* PackageManager.resolveContentProvider()}.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class ProviderInfo extends ComponentInfo
implements Parcelable {
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 36c03fd..25bb9e1 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -42,6 +42,7 @@
* information collected from the AndroidManifest.xml's
* <intent> tags.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ResolveInfo implements Parcelable {
private static final String TAG = "ResolveInfo";
private static final String INTENT_FORWARDER_ACTIVITY =
@@ -227,6 +228,7 @@
* item does not have a label, its name is returned.
*/
@NonNull
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public CharSequence loadLabel(@NonNull PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
@@ -304,6 +306,7 @@
* @return Returns a Drawable containing the resolution's icon. If the
* item does not have an icon, the default activity icon is returned.
*/
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.content.res.Resources.class)
public Drawable loadIcon(PackageManager pm) {
Drawable dr = null;
if (resolvePackageName != null && iconResourceId != 0) {
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9869179..4d704c3 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -31,6 +31,7 @@
* service. This corresponds to information collected from the
* AndroidManifest.xml's <service> tags.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ServiceInfo extends ComponentInfo
implements Parcelable {
/**
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index a69eee7..f173334 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -43,6 +43,7 @@
* <p>
* This class name is slightly misleading, since it's not actually a signature.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class Signature implements Parcelable {
private final byte[] mSignature;
private int mHashCode;
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 40592a1..3a00d91 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -24,3 +24,10 @@
# This flag is read in PackageParser at boot time, and in aapt2 which is a build tool.
is_fixed_read_only: true
}
+
+flag {
+ name: "nine_patch_frro"
+ namespace: "resource_manager"
+ description: "Feature flag for creating an frro from a 9-patch"
+ bug: "309232726"
+}
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index aafa7d5..f927b8b 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -17,12 +17,14 @@
package android.hardware.display;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ContentResolver;
import android.content.Context;
import android.metrics.LogMaker;
@@ -397,6 +399,8 @@
* @return {@code true} if the display is not at full saturation
* @hide
*/
+ @TestApi
+ @FlaggedApi(android.app.Flags.FLAG_MODES_API)
@RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
public boolean isSaturationActivated() {
return mManager.isSaturationActivated();
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 5cbc18e..cbeb821 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -16,7 +16,7 @@
}
flag {
- name: "role_controller_in_system_server"
+ name: "system_server_role_controller_enabled"
is_fixed_read_only: true
namespace: "permissions"
description: "enable role controller in system server"
diff --git a/core/java/android/service/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java
new file mode 100644
index 0000000..234ff4d
--- /dev/null
+++ b/core/java/android/service/notification/DeviceEffectsApplier.java
@@ -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.service.notification;
+
+/**
+ * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that
+ * make sense for the current platform.
+ * @hide
+ */
+public interface DeviceEffectsApplier {
+ /**
+ * Applies the {@link ZenDeviceEffects} to the device.
+ *
+ * <p>The supplied {@code effects} represents the "consolidated" device effects, i.e. the
+ * union of the effects of all the {@link ZenModeConfig.ZenRule} instances that are currently
+ * active. If no rules are active (or no active rules specify custom effects) then {@code
+ * effects} will be all-default (i.e. {@link ZenDeviceEffects#hasEffects} will return {@code
+ * false}.
+ *
+ * <p>This will be called whenever the set of consolidated effects changes (normally through
+ * the activation or deactivation of zen rules).
+ */
+ void apply(ZenDeviceEffects effects);
+}
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index db0b7ff..0e82b6c 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -18,6 +18,7 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -359,6 +360,27 @@
return this;
}
+ /**
+ * Applies the effects that are {@code true} on the supplied {@link ZenDeviceEffects} to
+ * this builder (essentially logically-ORing the effect set).
+ * @hide
+ */
+ @NonNull
+ public Builder add(@Nullable ZenDeviceEffects effects) {
+ if (effects == null) return this;
+ if (effects.shouldDisplayGrayscale()) setShouldDisplayGrayscale(true);
+ if (effects.shouldSuppressAmbientDisplay()) setShouldSuppressAmbientDisplay(true);
+ if (effects.shouldDimWallpaper()) setShouldDimWallpaper(true);
+ if (effects.shouldUseNightMode()) setShouldUseNightMode(true);
+ if (effects.shouldDisableAutoBrightness()) setShouldDisableAutoBrightness(true);
+ if (effects.shouldDisableTapToWake()) setShouldDisableTapToWake(true);
+ if (effects.shouldDisableTiltToWake()) setShouldDisableTiltToWake(true);
+ if (effects.shouldDisableTouch()) setShouldDisableTouch(true);
+ if (effects.shouldMinimizeRadioUsage()) setShouldMinimizeRadioUsage(true);
+ if (effects.shouldMaximizeDoze()) setShouldMaximizeDoze(true);
+ return this;
+ }
+
/** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
@NonNull
public ZenDeviceEffects build() {
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index f28574e..fd5517d 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -27,6 +27,9 @@
import com.android.window.flags.Flags;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
/**
* Provides an interface to the root-Surface of a View Hierarchy or Window. This
* is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -194,6 +197,42 @@
}
/**
+ * Add a trusted presentation listener on the SurfaceControl associated with this window.
+ *
+ * @param t Transaction that the trusted presentation listener is added on. This should
+ * be applied by the caller.
+ * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify
+ * when the to invoke the callback.
+ * @param executor The {@link Executor} where the callback will be invoked on.
+ * @param listener The {@link Consumer} that will receive the callbacks when entered or
+ * exited the threshold.
+ *
+ * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl,
+ * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer)
+ *
+ * @hide b/287076178 un-hide with API bump
+ */
+ default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+ @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+ }
+
+ /**
+ * Remove a trusted presentation listener on the SurfaceControl associated with this window.
+ *
+ * @param t Transaction that the trusted presentation listener removed on. This should
+ * be applied by the caller.
+ * @param listener The {@link Consumer} that was previously registered with
+ * addTrustedPresentationCallback that should be removed.
+ *
+ * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl)
+ * @hide b/287076178 un-hide with API bump
+ */
+ default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull Consumer<Boolean> listener) {
+ }
+
+ /**
* Transfer the currently in progress touch gesture from the host to the requested
* {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
* SurfaceControlViewHost was created with the current host's inputToken.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e3..17bbee6d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,8 +73,6 @@
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
import android.window.WindowContextInfo;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
/**
* System private interface to the window manager.
@@ -1077,10 +1075,4 @@
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MONITOR_INPUT)")
void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId);
-
- void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener,
- in TrustedPresentationThresholds thresholds, int id);
-
-
- void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1a4dbef..870ec4b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -12032,6 +12032,18 @@
scheduleTraversals();
}
+ @Override
+ public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.TrustedPresentationThresholds thresholds,
+ @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+ t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener);
+ }
+
+ @Override
+ public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t,
+ @NonNull Consumer<Boolean> listener) {
+ t.clearTrustedPresentationCallback(getSurfaceControl());
+ }
private void logAndTrace(String msg) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f668088..046ea77 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -122,9 +122,7 @@
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.window.ITrustedPresentationListener;
import android.window.TaskFpsCallback;
-import android.window.TrustedPresentationThresholds;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -5886,35 +5884,4 @@
default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) {
throw new UnsupportedOperationException();
}
-
- /**
- * Add a trusted presentation listener associated with a window. If the listener has already
- * been registered, an AndroidRuntimeException will be thrown.
- *
- * @param window The Window to add the trusted presentation listener for
- * @param thresholds The {@link TrustedPresentationThresholds} that will specify
- * when the to invoke the callback.
- * @param executor The {@link Executor} where the callback will be invoked on.
- * @param listener The {@link ITrustedPresentationListener} that will receive the callbacks
- * when entered or exited trusted presentation per the thresholds.
- *
- * @hide b/287076178 un-hide with API bump
- */
- default void registerTrustedPresentationListener(@NonNull IBinder window,
- @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
- @NonNull Consumer<Boolean> listener) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Removes a presentation listener associated with a window. If the listener was not previously
- * registered, the call will be a noop.
- *
- * @hide
- * @see #registerTrustedPresentationListener(IBinder,
- * TrustedPresentationThresholds, Executor, Consumer)
- */
- default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
- throw new UnsupportedOperationException();
- }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index a7d814e..214f1ec 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,13 +30,9 @@
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.view.inputmethod.InputMethodManager;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
import com.android.internal.util.FastPrintWriter;
@@ -47,7 +43,6 @@
import java.util.ArrayList;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
-import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
@@ -148,9 +143,6 @@
private Runnable mSystemPropertyUpdater;
- private final TrustedPresentationListener mTrustedPresentationListener =
- new TrustedPresentationListener();
-
private WindowManagerGlobal() {
}
@@ -332,7 +324,7 @@
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
- & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
@@ -490,7 +482,7 @@
if (who != null) {
WindowLeaked leak = new WindowLeaked(
what + " " + who + " has leaked window "
- + root.getView() + " that was originally added here");
+ + root.getView() + " that was originally added here");
leak.setStackTrace(root.getLocation().getStackTrace());
Log.e(TAG, "", leak);
}
@@ -798,86 +790,6 @@
}
}
- public void registerTrustedPresentationListener(@NonNull IBinder window,
- @NonNull TrustedPresentationThresholds thresholds, Executor executor,
- @NonNull Consumer<Boolean> listener) {
- mTrustedPresentationListener.addListener(window, thresholds, listener, executor);
- }
-
- public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
- mTrustedPresentationListener.removeListener(listener);
- }
-
- private final class TrustedPresentationListener extends
- ITrustedPresentationListener.Stub {
- private static int sId = 0;
- private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners =
- new ArrayMap<>();
-
- private final Object mTplLock = new Object();
-
- private void addListener(IBinder window, TrustedPresentationThresholds thresholds,
- Consumer<Boolean> listener, Executor executor) {
- synchronized (mTplLock) {
- if (mListeners.containsKey(listener)) {
- throw new AndroidRuntimeException("Trying to add duplicate listener");
- }
- int id = sId++;
- mListeners.put(listener, new Pair<>(id, executor));
- try {
- WindowManagerGlobal.getWindowManagerService()
- .registerTrustedPresentationListener(window, this, thresholds, id);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
- }
- }
-
- private void removeListener(Consumer<Boolean> listener) {
- synchronized (mTplLock) {
- var removedListener = mListeners.remove(listener);
- if (removedListener == null) {
- Log.i(TAG, "listener " + listener + " does not exist.");
- return;
- }
-
- try {
- WindowManagerGlobal.getWindowManagerService()
- .unregisterTrustedPresentationListener(this, removedListener.first);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
- }
- }
- }
-
- @Override
- public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds,
- int[] outOfTrustedStateListenerIds) {
- ArrayList<Runnable> firedListeners = new ArrayList<>();
- synchronized (mTplLock) {
- mListeners.forEach((listener, idExecutorPair) -> {
- final var listenerId = idExecutorPair.first;
- final var executor = idExecutorPair.second;
- for (int id : inTrustedStateListenerIds) {
- if (listenerId == id) {
- firedListeners.add(() -> executor.execute(
- () -> listener.accept(/*presentationState*/true)));
- }
- }
- for (int id : outOfTrustedStateListenerIds) {
- if (listenerId == id) {
- firedListeners.add(() -> executor.execute(
- () -> listener.accept(/*presentationState*/false)));
- }
- }
- });
- }
- for (int i = 0; i < firedListeners.size(); i++) {
- firedListeners.get(i).run();
- }
- }
- }
-
/** @hide */
public void addWindowlessRoot(ViewRootImpl impl) {
synchronized (mLock) {
@@ -889,7 +801,7 @@
public void removeWindowlessRoot(ViewRootImpl impl) {
synchronized (mLock) {
mWindowlessRoots.remove(impl);
- }
+ }
}
public void setRecentsAppBehindSystemBars(boolean behindSystemBars) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b4b1fde..d7b74b3 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -37,7 +37,6 @@
import android.util.Log;
import android.window.ITaskFpsCallback;
import android.window.TaskFpsCallback;
-import android.window.TrustedPresentationThresholds;
import android.window.WindowContext;
import android.window.WindowMetricsController;
import android.window.WindowProvider;
@@ -509,17 +508,4 @@
}
return false;
}
-
- @Override
- public void registerTrustedPresentationListener(@NonNull IBinder window,
- @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
- @NonNull Consumer<Boolean> listener) {
- mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
- }
-
- @Override
- public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
- mGlobal.unregisterTrustedPresentationListener(listener);
-
- }
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index d4cfd63..fab8c77 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -737,7 +737,7 @@
*/
public void onCancelAnimation(@AnimationType int animType) {
final int cujType = getImeInsetsCujFromAnimation(animType);
- if (cujType == -1) {
+ if (cujType != -1) {
InteractionJankMonitor.getInstance().cancel(cujType);
}
}
diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl
deleted file mode 100644
index b33128a..0000000
--- a/core/java/android/window/ITrustedPresentationListener.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.window;
-
-/**
- * @hide
- */
-oneway interface ITrustedPresentationListener {
- void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds);
-}
\ No newline at end of file
diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java
deleted file mode 100644
index 02fd6d9..0000000
--- a/core/java/android/window/TrustedPresentationListener.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 android.window;
-
-/**
- * @hide
- */
-public interface TrustedPresentationListener {
-
- void onTrustedPresentationChanged(boolean inTrustedPresentationState);
-
-}
diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl
deleted file mode 100644
index d7088bf..0000000
--- a/core/java/android/window/TrustedPresentationThresholds.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.window;
-
-parcelable TrustedPresentationThresholds;
diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java
deleted file mode 100644
index 801d35c..0000000
--- a/core/java/android/window/TrustedPresentationThresholds.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 android.window;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControl;
-
-import androidx.annotation.NonNull;
-
-/**
- * @hide
- */
-public final class TrustedPresentationThresholds implements Parcelable {
- /**
- * The min alpha the {@link SurfaceControl} is required to have to be considered inside the
- * threshold.
- */
- @FloatRange(from = 0f, fromInclusive = false, to = 1f)
- public final float mMinAlpha;
-
- /**
- * The min fraction of the SurfaceControl that was presented to the user to be considered
- * inside the threshold.
- */
- @FloatRange(from = 0f, fromInclusive = false, to = 1f)
- public final float mMinFractionRendered;
-
- /**
- * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold.
- */
- @IntRange(from = 1)
- public final int mStabilityRequirementMs;
-
- private void checkValid() {
- if (mMinAlpha <= 0 || mMinFractionRendered <= 0 || mStabilityRequirementMs < 1) {
- throw new IllegalArgumentException(
- "TrustedPresentationThresholds values are invalid");
- }
- }
-
- /**
- * Creates a new TrustedPresentationThresholds.
- *
- * @param minAlpha The min alpha the {@link SurfaceControl} is required to
- * have to be considered inside the
- * threshold.
- * @param minFractionRendered The min fraction of the SurfaceControl that was presented
- * to the user to be considered
- * inside the threshold.
- * @param stabilityRequirementMs The time in milliseconds required for the
- * {@link SurfaceControl} to be in the threshold.
- */
- public TrustedPresentationThresholds(
- @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha,
- @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered,
- @IntRange(from = 1) int stabilityRequirementMs) {
- this.mMinAlpha = minAlpha;
- this.mMinFractionRendered = minFractionRendered;
- this.mStabilityRequirementMs = stabilityRequirementMs;
- checkValid();
- }
-
- @Override
- public String toString() {
- return "TrustedPresentationThresholds { "
- + "minAlpha = " + mMinAlpha + ", "
- + "minFractionRendered = " + mMinFractionRendered + ", "
- + "stabilityRequirementMs = " + mStabilityRequirementMs
- + " }";
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeFloat(mMinAlpha);
- dest.writeFloat(mMinFractionRendered);
- dest.writeInt(mStabilityRequirementMs);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * @hide
- */
- TrustedPresentationThresholds(@NonNull Parcel in) {
- mMinAlpha = in.readFloat();
- mMinFractionRendered = in.readFloat();
- mStabilityRequirementMs = in.readInt();
-
- checkValid();
- }
-
- /**
- * @hide
- */
- public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR =
- new Creator<TrustedPresentationThresholds>() {
- @Override
- public TrustedPresentationThresholds[] newArray(int size) {
- return new TrustedPresentationThresholds[size];
- }
-
- @Override
- public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) {
- return new TrustedPresentationThresholds(in);
- }
- };
-}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 09be0cf..f03c993 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -5,4 +5,11 @@
namespace: "wear_frameworks"
description: "Allow out of focus process to update wallpaper complications"
bug: "271132915"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "multi_crop"
+ namespace: "systemui"
+ description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
+ bug: "281648899"
+}
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8c2a525..4bb7c33 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -93,7 +93,6 @@
WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM),
WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
- WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");
private final boolean mEnabled;
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index 0c39a69..358531a 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -46,6 +46,7 @@
jfieldID configuration;
jfieldID binaryDataOffset;
jfieldID binaryDataSize;
+ jfieldID isNinePatch;
} gFabricatedOverlayInternalEntryOffsets;
static struct parcel_file_descriptor_offsets_t {
@@ -288,13 +289,16 @@
env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataOffset);
const auto data_size =
env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataSize);
+ const auto nine_patch =
+ env->GetBooleanField(entry, gFabricatedOverlayInternalEntryOffsets.isNinePatch);
entries_params.push_back(
FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
(DataValue)data,
string_data.value_or(std::string()), binary_data,
static_cast<off64_t>(data_offset),
static_cast<size_t>(data_size),
- configuration.value_or(std::string())});
+ configuration.value_or(std::string()),
+ static_cast<bool>(nine_patch)});
ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
" binaryData = %d, configuration = %s",
resourceName.c_str(), dataType, data, string_data.value_or(std::string()).c_str(),
@@ -455,6 +459,9 @@
gFabricatedOverlayInternalEntryOffsets.binaryDataSize =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
"binaryDataSize", "J");
+ gFabricatedOverlayInternalEntryOffsets.isNinePatch =
+ GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "isNinePatch",
+ "Z");
jclass parcelFileDescriptorClass =
android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ba1f392..1229453 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -693,6 +693,16 @@
-->
</integer-array>
+ <!-- The device states (supplied by DeviceStateManager) that should be treated as concurrent
+ display state. Default is empty. -->
+ <integer-array name="config_concurrentDisplayDeviceStates">
+ <!-- Example:
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ -->
+ </integer-array>
+
<!-- Indicates whether the window manager reacts to half-fold device states by overriding
rotation. -->
<bool name="config_windowManagerHalfFoldAutoRotateOverride">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7787c5d..93aacdf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4142,6 +4142,7 @@
<java-symbol type="array" name="config_foldedDeviceStates" />
<java-symbol type="array" name="config_halfFoldedDeviceStates" />
<java-symbol type="array" name="config_rearDisplayDeviceStates" />
+ <java-symbol type="array" name="config_concurrentDisplayDeviceStates" />
<java-symbol type="bool" name="config_windowManagerHalfFoldAutoRotateOverride" />
<java-symbol type="bool" name="config_windowManagerPauseRotationWhenUnfolding" />
<java-symbol type="integer" name="config_pauseRotationWhenUnfolding_hingeEventTimeout" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 3cf28c9..69a6e6d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -535,6 +535,8 @@
<!-- Permission required for CTS test IntentRedirectionTest -->
<permission name="android.permission.QUERY_CLONED_APPS"/>
<permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
+ <!-- Permission required for CTS test NotificationManagerZenTest -->
+ <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 1912821..2237ba1 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -595,12 +595,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1518132958": {
- "message": "fractionRendered boundsOverSource=%f",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"-1517908912": {
"message": "requestScrollCapture: caught exception dispatching to window.token=%s",
"level": "WARN",
@@ -967,12 +961,6 @@
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
},
- "-1209762265": {
- "message": "Registering listener=%s with id=%d for window=%s with %s",
- "level": "DEBUG",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"-1209252064": {
"message": "Clear animatingExit: reason=clearAnimatingFlags win=%s",
"level": "DEBUG",
@@ -1345,12 +1333,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "-888703350": {
- "message": "Skipping %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -2821,12 +2803,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "360319850": {
- "message": "fractionRendered scale=%f",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"364992694": {
"message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s",
"level": "VERBOSE",
@@ -3007,12 +2983,6 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
- "532771960": {
- "message": "Adding untrusted state listener=%s with id=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"535103992": {
"message": "Wallpaper may change! Adjusting",
"level": "VERBOSE",
@@ -3091,12 +3061,6 @@
"group": "WM_DEBUG_DREAM",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "605179032": {
- "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"608694300": {
"message": " NEW SURFACE SESSION %s",
"level": "INFO",
@@ -3325,12 +3289,6 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowState.java"
},
- "824532141": {
- "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"829434921": {
"message": "Draw state now committed in %s",
"level": "VERBOSE",
@@ -3625,12 +3583,6 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
- "1090378847": {
- "message": "Checking %d windows",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1100065297": {
"message": "Attempted to get IME policy of a display that does not exist: %d",
"level": "WARN",
@@ -3763,12 +3715,6 @@
"group": "WM_DEBUG_FOCUS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1251721200": {
- "message": "unregister failed, couldn't find deathRecipient for %s with id=%d",
- "level": "ERROR",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1252594551": {
"message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
"level": "WARN",
@@ -3907,12 +3853,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
- "1382634842": {
- "message": "Unregistering listener=%s with id=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1393721079": {
"message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
"level": "VERBOSE",
@@ -3961,12 +3901,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1445704347": {
- "message": "coveredRegionsAbove updated with %s frame:%s region:%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1448683958": {
"message": "Override pending remote transitionSet=%b adapter=%s",
"level": "INFO",
@@ -4267,12 +4201,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
- "1786463281": {
- "message": "Adding trusted state listener=%s with id=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1789321832": {
"message": "Then token:%s is invalid. It might be removed",
"level": "WARN",
@@ -4447,12 +4375,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "1955470028": {
- "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f",
- "level": "VERBOSE",
- "group": "WM_DEBUG_TPL",
- "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java"
- },
"1964565370": {
"message": "Starting remote animation",
"level": "INFO",
@@ -4737,9 +4659,6 @@
"WM_DEBUG_TASKS": {
"tag": "WindowManager"
},
- "WM_DEBUG_TPL": {
- "tag": "WindowManager"
- },
"WM_DEBUG_WALLPAPER": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 5d16196..662a5c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -242,6 +242,18 @@
return mDeviceConfig.isLandscape();
}
+ /**
+ * On large screen (not small tablet), while in portrait, expanded bubbles are aligned to
+ * the bottom of the screen.
+ *
+ * @return whether bubbles are bottom aligned while expanded
+ */
+ public boolean areBubblesBottomAligned() {
+ return isLargeScreen()
+ && !mDeviceConfig.isSmallTablet()
+ && !isLandscape();
+ }
+
/** @return whether the screen is considered large. */
public boolean isLargeScreen() {
return mDeviceConfig.isLargeScreen();
@@ -417,7 +429,10 @@
- bottomPadding;
}
- private int getExpandedViewHeightForLargeScreen() {
+ /**
+ * Returns the height to use for the expanded view when showing on a large screen.
+ */
+ public int getExpandedViewHeightForLargeScreen() {
// the expanded view height on large tablets is calculated based on the shortest screen
// size and is the same in both portrait and landscape
int maxVerticalInset = Math.max(mInsets.top, mInsets.bottom);
@@ -460,13 +475,21 @@
boolean isOverflow = bubble == null || BubbleOverflow.KEY.equals(bubble.getKey());
float expandedViewHeight = getExpandedViewHeight(bubble);
float topAlignment = getExpandedViewYTopAligned();
+ int manageButtonHeight =
+ isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
+
+ // On largescreen portrait bubbles are bottom aligned.
+ if (areBubblesBottomAligned() && expandedViewHeight == MAX_HEIGHT) {
+ return mPositionRect.bottom - manageButtonHeight
+ - getExpandedViewHeightForLargeScreen() - mPointerWidth;
+ }
+
if (!showBubblesVertically() || expandedViewHeight == MAX_HEIGHT) {
// Top-align when bubbles are shown at the top or are max size.
return topAlignment;
}
+
// If we're here, we're showing vertically & developer has made height less than maximum.
- int manageButtonHeight =
- isOverflow ? mExpandedViewPadding : mManageButtonHeightIncludingMargins;
float pointerPosition = getPointerPosition(bubblePosition);
float bottomIfCentered = pointerPosition + (expandedViewHeight / 2) + manageButtonHeight;
float topIfCentered = pointerPosition - (expandedViewHeight / 2);
@@ -524,14 +547,8 @@
// Last bubble has screen index 0 and first bubble has max screen index value.
onScreenIndex = state.numberOfBubbles - 1 - index;
}
-
final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles);
- final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = showBubblesVertically
- ? mPositionRect.centerY()
- : mPositionRect.centerX();
- // alignment - centered on the edge
- final float rowStart = centerPosition - (expandedStackSize / 2f);
+ final float rowStart = getBubbleRowStart(state);
float x;
float y;
if (showBubblesVertically) {
@@ -557,6 +574,25 @@
return new PointF(x, y);
}
+ private float getBubbleRowStart(BubbleStackView.StackViewState state) {
+ final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
+ final float rowStart;
+ if (areBubblesBottomAligned()) {
+ final float expandedViewHeight = getExpandedViewHeightForLargeScreen();
+ final float expandedViewBottom = mScreenRect.bottom
+ - Math.max(mInsets.bottom, mInsets.top)
+ - mManageButtonHeight - mPointerWidth;
+ final float expandedViewCenter = expandedViewBottom - (expandedViewHeight / 2f);
+ rowStart = expandedViewCenter - (expandedStackSize / 2f);
+ } else {
+ final float centerPosition = showBubblesVertically()
+ ? mPositionRect.centerY()
+ : mPositionRect.centerX();
+ rowStart = centerPosition - (expandedStackSize / 2f);
+ }
+ return rowStart;
+ }
+
/**
* Returns the position of the bubble on-screen when the stack is expanded and the IME
* is showing.
@@ -577,9 +613,8 @@
final float bottomHeight = getImeHeight() + mInsets.bottom + (mSpacingBetweenBubbles * 2);
final float bottomInset = mScreenRect.bottom - bottomHeight;
final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = mPositionRect.centerY();
- final float rowBottom = centerPosition + (expandedStackSize / 2f);
- final float rowTop = centerPosition - (expandedStackSize / 2f);
+ final float rowTop = getBubbleRowStart(state);
+ final float rowBottom = rowTop + expandedStackSize;
float rowTopForIme = rowTop;
if (rowBottom > bottomInset) {
// We overlap with IME, must shift the bubbles
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index e734300..49db8d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -26,11 +26,12 @@
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
public class SplitScreenConstants {
- /**
- * Duration used for every split fade-in or fade-out.
- */
+ /** Duration used for every split fade-in or fade-out. */
public static final int FADE_DURATION = 133;
+ /** Key for passing in widget intents when invoking split from launcher workspace. */
+ public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
+
///////////////
// IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants:
// These int values must not be changed -- they are persisted to user-defined app pairs, and
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 162ce19..a31a773 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -40,6 +40,7 @@
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
@@ -246,8 +247,15 @@
@SplitPosition int position) {
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
- final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
- ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+ final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+ baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ final Bundle opts = baseActivityOpts.toBundle();
+ if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+ opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+ }
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
if (isTask) {
@@ -259,9 +267,6 @@
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
- // Put BAL flags to avoid activity start aborted.
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
- opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
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 37b24e5..56f1c78 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
@@ -25,6 +25,7 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -719,10 +720,10 @@
// recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
- fillInIntent = new Intent();
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ setSecondIntentMultipleTask = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
if (mRecentTasksOptional.isPresent()) {
@@ -737,6 +738,10 @@
Toast.LENGTH_SHORT).show();
}
}
+ if (options2 != null) {
+ Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+ fillInIntent = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+ }
mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
options2, splitPosition, snapPosition, remoteTransition, instanceId);
}
@@ -787,12 +792,12 @@
? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
final ActivityOptions activityOptions2 = options2 != null
? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
+ boolean setSecondIntentMultipleTask = false;
if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- fillInIntent2 = new Intent();
- fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ setSecondIntentMultipleTask = true;
if (shortcutInfo1 != null) {
activityOptions1.setApplyMultipleTaskFlagForShortcut(true);
@@ -811,6 +816,10 @@
Toast.LENGTH_SHORT).show();
}
}
+ if (options2 != null) {
+ Intent widgetIntent = options2.getParcelable(KEY_EXTRA_WIDGET_INTENT, Intent.class);
+ fillInIntent2 = resolveWidgetFillinIntent(widgetIntent, setSecondIntentMultipleTask);
+ }
mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1,
activityOptions1.toBundle(), pendingIntent2, fillInIntent2, shortcutInfo2,
activityOptions2.toBundle(), splitPosition, snapPosition, remoteTransition,
@@ -916,6 +925,34 @@
return false;
}
+ /**
+ * Determines whether the widgetIntent needs to be modified if multiple tasks of its
+ * corresponding package/app are supported. There are 4 possible paths:
+ * <li> We select a widget for second app which is the same as the first app </li>
+ * <li> We select a widget for second app which is different from the first app </li>
+ * <li> No widgets involved, we select a second app that is the same as first app </li>
+ * <li> No widgets involved, we select a second app that is different from the first app
+ * (returns null) </li>
+ *
+ * @return an {@link Intent} with the appropriate {@link Intent#FLAG_ACTIVITY_MULTIPLE_TASK}
+ * added on or not depending on {@param launchMultipleTasks}.
+ */
+ @Nullable
+ private Intent resolveWidgetFillinIntent(@Nullable Intent widgetIntent,
+ boolean launchMultipleTasks) {
+ Intent fillInIntent2 = null;
+ if (launchMultipleTasks && widgetIntent != null) {
+ fillInIntent2 = widgetIntent;
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ } else if (widgetIntent != null) {
+ fillInIntent2 = widgetIntent;
+ } else if (launchMultipleTasks) {
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ return fillInIntent2;
+ }
+
RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
if (ENABLE_SHELL_TRANSITIONS) return null;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
index e5ae6e5..6ebee73 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -332,6 +332,201 @@
.isWithin(0.1f).of(expectedHeight);
}
+ @Test
+ public void testAreBubblesBottomAligned_largeScreen_true() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isTrue();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_largeScreen_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_smallTablet_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setSmallTablet()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testAreBubblesBottomAligned_phone_false() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ assertThat(mPositioner.areBubblesBottomAligned()).isFalse();
+ }
+
+ @Test
+ public void testExpandedViewY_phoneLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height so it'll always be top aligned
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_phonePortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // Always top aligned in phone portrait
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_smallTabletLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setSmallTablet()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_smallTabletPortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setSmallTablet()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on small tablets
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_largeScreenLandscape() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setLandscape()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ // This bubble will have max height which is always top aligned on landscape, large tablet
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(mPositioner.getExpandedViewYTopAligned());
+ }
+
+ @Test
+ public void testExpandedViewY_largeScreenPortrait() {
+ Insets insets = Insets.of(10, 20, 5, 15);
+ Rect screenBounds = new Rect(0, 0, 1800, 2600);
+
+ DeviceConfig deviceConfig = new ConfigBuilder()
+ .setLargeScreen()
+ .setInsets(insets)
+ .setScreenBounds(screenBounds)
+ .build();
+ mPositioner.update(deviceConfig);
+
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1), null, directExecutor());
+
+ int manageButtonHeight =
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_manage_button_height);
+ int manageButtonPlusMargin = manageButtonHeight + 2
+ * mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_button_margin);
+ int pointerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_pointer_width);
+
+ final float expectedExpandedViewY = mPositioner.getAvailableRect().bottom
+ - manageButtonPlusMargin
+ - mPositioner.getExpandedViewHeightForLargeScreen()
+ - pointerWidth;
+
+ // Bubbles are bottom aligned on portrait, large tablet
+ assertThat(mPositioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
+ .isEqualTo(expectedExpandedViewY);
+ }
+
/**
* Calculates the Y position bubbles should be placed based on the config. Based on
* the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
diff --git a/libs/androidfw/FileStream.cpp b/libs/androidfw/FileStream.cpp
index b86c9cb..e898949 100644
--- a/libs/androidfw/FileStream.cpp
+++ b/libs/androidfw/FileStream.cpp
@@ -22,6 +22,7 @@
#include "android-base/errors.h"
#include "android-base/file.h" // for O_BINARY
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "android-base/utf8.h"
@@ -37,9 +38,9 @@
namespace android {
FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
- : buffer_capacity_(buffer_capacity) {
+ : should_close_(true), buffer_capacity_(buffer_capacity) {
int mode = O_RDONLY | O_CLOEXEC | O_BINARY;
- fd_.reset(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode)));
+ fd_ = TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), mode));
if (fd_ == -1) {
error_ = SystemErrorCodeToString(errno);
} else {
@@ -48,7 +49,7 @@
}
FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
- : fd_(fd), buffer_capacity_(buffer_capacity) {
+ : fd_(fd), should_close_(true), buffer_capacity_(buffer_capacity) {
if (fd_ < 0) {
error_ = "Bad File Descriptor";
} else {
@@ -56,6 +57,17 @@
}
}
+FileInputStream::FileInputStream(android::base::borrowed_fd fd, size_t buffer_capacity)
+ : fd_(fd.get()), should_close_(false), buffer_capacity_(buffer_capacity) {
+
+ if (fd_ < 0) {
+ error_ = "Bad File Descriptor";
+ } else {
+ buffer_.reset(new uint8_t[buffer_capacity_]);
+ }
+}
+
+
bool FileInputStream::Next(const void** data, size_t* size) {
if (HadError()) {
return false;
@@ -73,7 +85,12 @@
ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
if (n < 0) {
error_ = SystemErrorCodeToString(errno);
- fd_.reset();
+ if (fd_ != -1) {
+ if (should_close_) {
+ close(fd_);
+ }
+ fd_ = -1;
+ }
buffer_.reset();
return false;
}
diff --git a/libs/androidfw/include/androidfw/BigBufferStream.h b/libs/androidfw/include/androidfw/BigBufferStream.h
index e55fe0d..c23194b 100644
--- a/libs/androidfw/include/androidfw/BigBufferStream.h
+++ b/libs/androidfw/include/androidfw/BigBufferStream.h
@@ -24,8 +24,13 @@
class BigBufferInputStream : public KnownSizeInputStream {
public:
inline explicit BigBufferInputStream(const BigBuffer* buffer)
- : buffer_(buffer), iter_(buffer->begin()) {
+ : owning_buffer_(0), buffer_(buffer), iter_(buffer->begin()) {
}
+
+ inline explicit BigBufferInputStream(android::BigBuffer&& buffer)
+ : owning_buffer_(std::move(buffer)), buffer_(&owning_buffer_), iter_(buffer_->begin()) {
+ }
+
virtual ~BigBufferInputStream() = default;
bool Next(const void** data, size_t* size) override;
@@ -47,6 +52,7 @@
private:
DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+ android::BigBuffer owning_buffer_;
const BigBuffer* buffer_;
BigBuffer::const_iterator iter_;
size_t offset_ = 0;
diff --git a/libs/androidfw/include/androidfw/FileStream.h b/libs/androidfw/include/androidfw/FileStream.h
index fb84a91..87c42d1 100644
--- a/libs/androidfw/include/androidfw/FileStream.h
+++ b/libs/androidfw/include/androidfw/FileStream.h
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
+#include <unistd.h>
#include "Streams.h"
#include "android-base/macros.h"
@@ -35,6 +36,16 @@
// Take ownership of `fd`.
explicit FileInputStream(int fd, size_t buffer_capacity = kDefaultBufferCapacity);
+ // Take ownership of `fd`.
+ explicit FileInputStream(android::base::borrowed_fd fd,
+ size_t buffer_capacity = kDefaultBufferCapacity);
+
+ ~FileInputStream() {
+ if (should_close_ && (fd_ != -1)) {
+ close(fd_);
+ }
+ }
+
bool Next(const void** data, size_t* size) override;
void BackUp(size_t count) override;
@@ -50,8 +61,9 @@
private:
DISALLOW_COPY_AND_ASSIGN(FileInputStream);
- android::base::unique_fd fd_;
+ int fd_ = -1;
std::string error_;
+ bool should_close_;
std::unique_ptr<uint8_t[]> buffer_;
size_t buffer_capacity_ = 0u;
size_t buffer_offset_ = 0u;
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 865a298..d1dda81 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -17,10 +17,15 @@
#ifndef _ANDROID_DIAGNOSTICS_H
#define _ANDROID_DIAGNOSTICS_H
+// on some systems ERROR is defined as 0 so android::base::ERROR becomes android::base::0
+// which doesn't compile. We undef it here to avoid that and because we don't ever need that def.
+#undef ERROR
+
#include <sstream>
#include <string>
#include "Source.h"
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
@@ -144,6 +149,36 @@
DISALLOW_COPY_AND_ASSIGN(NoOpDiagnostics);
};
+class AndroidLogDiagnostics : public IDiagnostics {
+ public:
+ AndroidLogDiagnostics() = default;
+
+ void Log(Level level, DiagMessageActual& actual_msg) override {
+ android::base::LogSeverity severity;
+ switch (level) {
+ case Level::Error:
+ severity = android::base::ERROR;
+ break;
+
+ case Level::Warn:
+ severity = android::base::WARNING;
+ break;
+
+ case Level::Note:
+ severity = android::base::INFO;
+ break;
+ }
+ if (!actual_msg.source.path.empty()) {
+ LOG(severity) << actual_msg.source << ": " + actual_msg.message;
+ } else {
+ LOG(severity) << actual_msg.message;
+ }
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(AndroidLogDiagnostics);
+};
+
+
} // namespace android
#endif /* _ANDROID_DIAGNOSTICS_H */
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index fdb3551..c0514fd 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1875,6 +1875,7 @@
off64_t binary_data_offset;
size_t binary_data_size;
std::string configuration;
+ bool nine_patch;
};
class AssetManager2;
diff --git a/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml
new file mode 100644
index 0000000..a0426a3
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable-night/ic_permission_media_routing_control.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="@android:color/system_accent1_200">
+ <path android:fillColor="@android:color/system_accent1_200"
+ android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600L680,600Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml b/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml
new file mode 100644
index 0000000..9a7525b
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/drawable/ic_permission_media_routing_control.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/system_accent1_600"
+ android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600L680,600Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 7a6fad4..281eba6 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -149,6 +149,9 @@
<!-- Nearby devices' permission will be granted of corresponding profile [CHAR LIMIT=30] -->
<string name="permission_nearby_devices">Nearby devices</string>
+ <!-- Change media output permission will be granted to the corresponding profile [CHAR LIMIT=30] -->
+ <string name="permission_media_routing_control">Change media output</string>
+
<!-- Storage permission will be granted of corresponding profile [CHAR LIMIT=30] -->
<string name="permission_storage">Photos and media</string>
@@ -194,6 +197,9 @@
<!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
+ <!-- Description of change media output permission to be granted to the corresponding profile [CHAR LIMIT=NONE] -->
+ <string name="permission_media_routing_control_summary">Access a list of available devices and control which one streams or casts audio or video from other apps</string>
+
<!-- The type of the device for phone [CHAR LIMIT=30] -->
<string name="device_type" product="default">phone</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 060c032..551e975 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -26,6 +26,7 @@
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS;
+import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
@@ -41,6 +42,8 @@
import android.util.ArrayMap;
import android.util.ArraySet;
+import com.android.media.flags.Flags;
+
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -73,9 +76,15 @@
PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
- map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
- PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
- PERMISSION_NEARBY_DEVICES));
+ if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
+ map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
+ PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+ PERMISSION_NEARBY_DEVICES));
+ } else {
+ map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
+ PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+ PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
+ }
map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
PERMISSION_NEARBY_DEVICES));
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index 7ed1816..e21aee3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -54,6 +54,7 @@
static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
static final int PERMISSION_MICROPHONE = 9;
static final int PERMISSION_CALL_LOGS = 10;
+ static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11;
private static final Map<Integer, Integer> sTitleMap;
static {
@@ -69,6 +70,7 @@
map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
+ map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
sTitleMap = unmodifiableMap(map);
}
@@ -87,6 +89,7 @@
R.string.permission_nearby_device_streaming_summary);
map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
+ map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
sSummaryMap = unmodifiableMap(map);
}
@@ -105,6 +108,7 @@
R.drawable.ic_permission_nearby_device_streaming);
map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
+ map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
sIconMap = unmodifiableMap(map);
}
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index b7ce518..be1e888 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -9,5 +9,10 @@
{
"name": "SettingsSpaUnitTests"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "SpaScreenshotTests"
+ }
]
}
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
index 74113d8..1a59722 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
index 0d22c6a..69017d0 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
index f77b8a7..dc2e594 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
index 9372791..7e1de6a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
index dda9e9e..2218b5b 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
index bf19a2c..e18f42b 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
index b14e196e..21291a7 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
index c77f9b1..5c1a0e24 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
index f513830..4003506 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
index dda6f0a..1c669a6 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
index 7468169..101eb0c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
index 669f443..9cb16ef 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
index 8e37cc0..20b11e3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
index b0543e0..c2ad8ad 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
index 3755928..3408cca 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
index f77b8a7..dc2e594 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
index 7800149..c3ca8cd 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
index be40959..53b92dd 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
index 2d5894e..caa26a3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
index 2a54b9e..02a943d 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
index b0fc305..c1abbc2 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
index 73f2407..cdb62f9 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
index 213c0b2..b3ae6b3 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
index 7468169..101eb0c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
index ce7ab78..4227bb7 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
index 9d92f7a..52c4341 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
index 5255d14..eebac76 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
index 8f3f664..477d16c 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
index cc1de55..0dc83f9 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
index e29f26a..f32d7421 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
index 8b9bc5c..1de3b7f 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
index b1676b3..5e5ed6a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
index 2a7b341..c133a6e 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
index 4845ea8..55acf95 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
index 6e860d3..f72dbc2 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
index d1abaa6..5a1c85d 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
index c211e0e..bc8144e 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
index 9c57e33..2b0351a 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
index d416a0b..bfcdb55 100644
--- a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
index 2c60db4..8c52d57 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlow.kt
@@ -21,13 +21,17 @@
import android.content.Intent
import android.content.IntentFilter
import android.os.UserHandle
+import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flowOn
+private const val TAG = "BroadcastReceiverAsUser"
+
/**
* A [BroadcastReceiver] flow for the given [intentFilter].
*/
@@ -50,4 +54,6 @@
)
awaitClose { unregisterReceiver(broadcastReceiver) }
+}.catch { e ->
+ Log.e(TAG, "Error while broadcastReceiverAsUserFlow", e)
}.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
index dfb8e22..9cb33d2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverAsUserFlowTest.kt
@@ -32,9 +32,11 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.isNull
import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
@RunWith(AndroidJUnit4::class)
class BroadcastReceiverAsUserFlowTest {
@@ -83,6 +85,18 @@
assertThat(onReceiveIsCalled).isTrue()
}
+ @Test
+ fun broadcastReceiverAsUserFlow_unregisterReceiverThrowException_noCrash() = runBlocking {
+ context.stub {
+ on { unregisterReceiver(any()) } doThrow IllegalArgumentException()
+ }
+ val flow = context.broadcastReceiverAsUserFlow(INTENT_FILTER, USER_HANDLE)
+
+ flow.firstWithTimeoutOrNull()
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
private companion object {
val USER_HANDLE: UserHandle = UserHandle.of(0)
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b6a0c7b..d12d9d6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -562,6 +562,9 @@
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
+ <!-- Permissions required for CTS test - NotificationManagerZenTest -->
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<uses-permission android:name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<uses-permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7cf562f..c2c5e00 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -190,6 +190,7 @@
"androidx.room_room-runtime",
"androidx.room_room-ktx",
"com.google.android.material_material",
+ "device_state_flags_lib",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
"iconloader_base",
@@ -302,6 +303,7 @@
"androidx.exifinterface_exifinterface",
"androidx.room_room-runtime",
"androidx.room_room-ktx",
+ "device_state_flags_lib",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
"kotlinx_coroutines_test",
diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml
index 3f65aa7..8d7f7eb 100644
--- a/packages/SystemUI/res/layout/connected_display_dialog.xml
+++ b/packages/SystemUI/res/layout/connected_display_dialog.xml
@@ -45,6 +45,15 @@
android:text="@string/connected_display_dialog_start_mirroring"
android:textAppearance="@style/TextAppearance.Dialog.Title" />
+ <TextView
+ android:id="@+id/dual_display_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:visibility="gone"
+ android:text="@string/connected_display_dialog_dual_display_stop_warning"
+ android:textAppearance="@style/TextAppearance.Dialog.Body" />
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f49d2a1..7ca0b6e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3245,6 +3245,8 @@
<!--- 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>
+ <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
+ <string name="connected_display_dialog_dual_display_stop_warning">Any dual screen activity currently running will be stopped</string>
<!--- Label of the "enable display" button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="mirror_display">Mirror display</string>
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 341d214..dd4ca92 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -21,7 +21,6 @@
import android.app.admin.DevicePolicyManager
import android.content.IntentFilter
import android.os.UserHandle
-import com.android.internal.widget.LockPatternChecker
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
@@ -40,8 +39,6 @@
import dagger.Module
import java.util.function.Function
import javax.inject.Inject
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -193,8 +190,8 @@
override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> =
MutableStateFlow(null)
- private val UserRepository.selectedUserId: Int
- get() = getSelectedUserInfo().id
+ private val selectedUserId: Int
+ get() = userRepository.getSelectedUserInfo().id
override val authenticationMethod: Flow<AuthenticationMethodModel> =
combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
@@ -233,19 +230,15 @@
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return withContext(backgroundDispatcher) {
- blockingAuthenticationMethodInternal(userRepository.selectedUserId)
+ blockingAuthenticationMethodInternal(selectedUserId)
}
}
override suspend fun getPinLength(): Int {
- return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUserId
- lockPatternUtils.getPinLength(selectedUserId)
- }
+ return withContext(backgroundDispatcher) { lockPatternUtils.getPinLength(selectedUserId) }
}
override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
- val selectedUserId = userRepository.selectedUserId
withContext(backgroundDispatcher) {
if (isSuccessful) {
lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
@@ -258,52 +251,32 @@
override suspend fun getFailedAuthenticationAttemptCount(): Int {
return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUserId
lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
}
}
override suspend fun getThrottlingEndTimestamp(): Long {
return withContext(backgroundDispatcher) {
- val selectedUserId = userRepository.selectedUserId
lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
}
}
override suspend fun setThrottleDuration(durationMs: Int) {
withContext(backgroundDispatcher) {
- lockPatternUtils.setLockoutAttemptDeadline(
- userRepository.selectedUserId,
- durationMs,
- )
+ lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
}
}
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
- return suspendCoroutine { continuation ->
- LockPatternChecker.checkCredential(
- lockPatternUtils,
- credential,
- userRepository.selectedUserId,
- object : LockPatternChecker.OnCheckCallback {
- override fun onChecked(matched: Boolean, throttleTimeoutMs: Int) {
- continuation.resume(
- AuthenticationResultModel(
- isSuccessful = matched,
- throttleDurationMs = throttleTimeoutMs,
- )
- )
- }
-
- override fun onCancelled() {
- continuation.resume(AuthenticationResultModel(isSuccessful = false))
- }
-
- override fun onEarlyMatched() = Unit
- }
- )
+ return withContext(backgroundDispatcher) {
+ try {
+ val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {}
+ AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0)
+ } catch (ex: LockPatternUtils.RequestThrottledException) {
+ AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 1ba0220..eb87505 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -45,6 +45,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Hosts application business logic related to user authentication.
@@ -227,6 +228,10 @@
// Since authentication succeeded, we should refresh throttling to make sure that our
// state is completely reflecting the upstream source of truth.
refreshThrottling()
+
+ // Force a garbage collection in an attempt to erase any credentials left in memory.
+ // Do it after a 5-sec delay to avoid making the bouncer dismiss animation janky.
+ initiateGarbageCollection(delayMs = 5000)
}
return if (authenticationResult.isSuccessful) {
@@ -310,6 +315,15 @@
}
}
+ private suspend fun initiateGarbageCollection(delayMs: Long) {
+ withContext(backgroundDispatcher) {
+ delay(delayMs)
+ System.gc()
+ System.runFinalization()
+ System.gc()
+ }
+ }
+
companion object {
const val TAG = "AuthenticationInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index b1a153a..24cd9b5 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -66,6 +66,8 @@
public static final int MODE_ON = 1;
public static final int MODE_OFF = 2;
public static final int MODE_ESTIMATE = 3;
+ @VisibleForTesting
+ public static final long LAYOUT_TRANSITION_DURATION = 200;
private final AccessorizedBatteryDrawable mDrawable;
private final ImageView mBatteryIconView;
@@ -134,7 +136,7 @@
private void setupLayoutTransition() {
LayoutTransition transition = new LayoutTransition();
- transition.setDuration(200);
+ transition.setDuration(LAYOUT_TRANSITION_DURATION);
// Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
// and disables all other animation types
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index ff23837..b0143f5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -60,6 +60,8 @@
val currentRotation: StateFlow<DisplayRotation>
}
+// TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
+// instead.
@SysUISingleton
class DisplayStateRepositoryImpl
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
index 65cd84b..373279c 100644
--- a/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.display
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepositoryImpl
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayRepositoryImpl
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
@@ -32,4 +34,9 @@
): ConnectedDisplayInteractor
@Binds fun bindsDisplayRepository(displayRepository: DisplayRepositoryImpl): DisplayRepository
+
+ @Binds
+ fun bindsDeviceStateRepository(
+ deviceStateRepository: DeviceStateRepositoryImpl
+ ): DeviceStateRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
new file mode 100644
index 0000000..83337f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DeviceStateRepository.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.internal.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+interface DeviceStateRepository {
+ val state: StateFlow<DeviceState>
+
+ enum class DeviceState {
+ /** Device state in [R.array.config_foldedDeviceStates] */
+ FOLDED,
+ /** Device state in [R.array.config_halfFoldedDeviceStates] */
+ HALF_FOLDED,
+ /** Device state in [R.array.config_openDeviceStates] */
+ UNFOLDED,
+ /** Device state in [R.array.config_rearDisplayDeviceStates] */
+ REAR_DISPLAY,
+ /** Device state in [R.array.config_concurrentDisplayDeviceStates] */
+ CONCURRENT_DISPLAY,
+ /** Device state in none of the other arrays. */
+ UNKNOWN,
+ }
+}
+
+class DeviceStateRepositoryImpl
+@Inject
+constructor(
+ context: Context,
+ deviceStateManager: DeviceStateManager,
+ @Background bgScope: CoroutineScope,
+ @Background executor: Executor
+) : DeviceStateRepository {
+
+ override val state: StateFlow<DeviceState> =
+ conflatedCallbackFlow {
+ val callback =
+ DeviceStateManager.DeviceStateCallback { state ->
+ trySend(deviceStateToPosture(state))
+ }
+ deviceStateManager.registerCallback(executor, callback)
+ awaitClose { deviceStateManager.unregisterCallback(callback) }
+ }
+ .stateIn(bgScope, started = SharingStarted.WhileSubscribed(), DeviceState.UNKNOWN)
+
+ private fun deviceStateToPosture(deviceStateId: Int): DeviceState {
+ return deviceStateMap.firstOrNull { (ids, _) -> deviceStateId in ids }?.deviceState
+ ?: DeviceState.UNKNOWN
+ }
+
+ private val deviceStateMap =
+ listOf(
+ R.array.config_foldedDeviceStates to DeviceState.FOLDED,
+ R.array.config_halfFoldedDeviceStates to DeviceState.HALF_FOLDED,
+ R.array.config_openDeviceStates to DeviceState.UNFOLDED,
+ R.array.config_rearDisplayDeviceStates to DeviceState.REAR_DISPLAY,
+ R.array.config_concurrentDisplayDeviceStates to DeviceState.CONCURRENT_DISPLAY,
+ )
+ .map { IdsPerDeviceState(context.resources.getIntArray(it.first).toSet(), it.second) }
+
+ private data class IdsPerDeviceState(val ids: Set<Int>, val deviceState: DeviceState)
+}
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 20a9e5d..73b7a8a 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
@@ -21,6 +21,7 @@
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DeviceStateRepository
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
@@ -55,6 +56,9 @@
/** Pending display that can be enabled to be used by the system. */
val pendingDisplay: Flow<PendingDisplay?>
+ /** Pending display that can be enabled to be used by the system. */
+ val concurrentDisplaysInProgress: Flow<Boolean>
+
/** Possible connected display state. */
enum class State {
DISCONNECTED,
@@ -84,6 +88,7 @@
private val virtualDeviceManager: VirtualDeviceManager,
keyguardRepository: KeyguardRepository,
displayRepository: DisplayRepository,
+ deviceStateRepository: DeviceStateRepository,
@Background backgroundCoroutineDispatcher: CoroutineDispatcher,
) : ConnectedDisplayInteractor {
@@ -128,9 +133,16 @@
}
}
+ override val concurrentDisplaysInProgress: Flow<Boolean> =
+ deviceStateRepository.state
+ .map { it == DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineDispatcher)
+
private fun DisplayRepository.PendingDisplay.toInteractorPendingDisplay(): PendingDisplay =
object : PendingDisplay {
override suspend fun enable() = this@toInteractorPendingDisplay.enable()
+
override suspend fun ignore() = this@toInteractorPendingDisplay.ignore()
}
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
index d500d1c2..c0a873a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -37,11 +37,13 @@
private val onCancelMirroring: View.OnClickListener,
private val navbarBottomInsetsProvider: () -> Int,
configurationController: ConfigurationController? = null,
+ private val showConcurrentDisplayInfo: Boolean = false,
theme: Int = R.style.Theme_SystemUI_Dialog,
) : SystemUIBottomSheetDialog(context, configurationController, theme) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
+ private lateinit var dualDisplayWarning: TextView
private var enabledPressed = false
override fun onCreate(savedInstanceState: Bundle?) {
@@ -56,6 +58,11 @@
dismissButton =
requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
+ dualDisplayWarning =
+ requireViewById<TextView>(R.id.dual_display_warning).apply {
+ visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
+ }
+
setOnDismissListener {
if (!enabledPressed) {
onCancelMirroring.onClick(null)
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
index 19b4d22..10aa703 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -17,6 +17,7 @@
import android.app.Dialog
import android.content.Context
+import com.android.server.policy.feature.flags.Flags
import com.android.systemui.biometrics.Utils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -28,8 +29,9 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
/**
@@ -44,25 +46,33 @@
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
private var dialog: Dialog? = null
/** Starts listening for pending displays. */
fun init() {
- connectedDisplayInteractor.pendingDisplay
- .onEach { pendingDisplay ->
+ val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
+ val concurrentDisplaysInProgessFlow =
+ if (Flags.enableDualDisplayBlocking()) {
+ connectedDisplayInteractor.concurrentDisplaysInProgress
+ } else {
+ flow { emit(false) }
+ }
+ pendingDisplayFlow
+ .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
+ ->
if (pendingDisplay == null) {
hideDialog()
} else {
- showDialog(pendingDisplay)
+ showDialog(pendingDisplay, concurrentDisplaysInProgress)
}
}
.launchIn(scope)
}
- private fun showDialog(pendingDisplay: PendingDisplay) {
+ private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
hideDialog()
dialog =
MirroringConfirmationDialog(
@@ -77,6 +87,7 @@
},
navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
configurationController,
+ showConcurrentDisplayInfo = concurrentDisplaysInProgess
)
.apply { show() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 11d6507..e8ceabf 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -438,12 +438,6 @@
val LOCKSCREEN_ENABLE_LANDSCAPE =
unreleasedFlag("lockscreen.enable_landscape")
- // TODO(b/281648899): Tracking bug
- @Keep
- @JvmField
- val WALLPAPER_MULTI_CROP =
- sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
-
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8397caa..dd194eaa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -794,6 +794,13 @@
/** update Qs height state */
public void setExpansionHeight(float height) {
+ // TODO(b/277909752): remove below log when bug is fixed
+ if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0
+ && mBarState == SHADE) {
+ Log.wtf(TAG,
+ "setting QS height to 0 in split shade while shade is open(ing). "
+ + "Value of isExpandImmediate() = " + isExpandImmediate());
+ }
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
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 6cb079a..b6d4ded 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
@@ -139,7 +139,6 @@
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
- private static final SourceType PINNED = SourceType.from("Pinned");
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
@@ -147,7 +146,6 @@
private boolean mIsSnoozed;
private boolean mShowSnooze = false;
private boolean mIsFaded;
- private boolean mAnimatePinnedRoundness = false;
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -1053,14 +1051,6 @@
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
- if (pinned) {
- // Should be animated if someone explicitly set it to 0 and the row is shown.
- boolean animated = mAnimatePinnedRoundness && isShown();
- requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
- } else {
- requestRoundnessReset(PINNED);
- mAnimatePinnedRoundness = true;
- }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index d635f89..bf0c823 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -29,7 +29,7 @@
TAG,
LogLevel.ERROR,
{ str1 = logKey(key) },
- { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" }
+ { "Heads up view appearing $str1 for ANIMATION_TYPE_ADD" }
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
new file mode 100644
index 0000000..21b8aca
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DeviceStateRepositoryTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.data.repository
+
+import android.hardware.devicestate.DeviceStateManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+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.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DeviceStateRepositoryTest : SysuiTestCase() {
+
+ private val deviceStateManager = mock<DeviceStateManager>()
+ private val deviceStateManagerListener =
+ kotlinArgumentCaptor<DeviceStateManager.DeviceStateCallback>()
+
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var deviceStateRepository: DeviceStateRepositoryImpl
+
+ @Before
+ fun setup() {
+ mContext.orCreateTestableResources.apply {
+ addOverride(R.array.config_foldedDeviceStates, listOf(TEST_FOLDED).toIntArray())
+ addOverride(R.array.config_halfFoldedDeviceStates, TEST_HALF_FOLDED.toIntArray())
+ addOverride(R.array.config_openDeviceStates, TEST_UNFOLDED.toIntArray())
+ addOverride(R.array.config_rearDisplayDeviceStates, TEST_REAR_DISPLAY.toIntArray())
+ addOverride(
+ R.array.config_concurrentDisplayDeviceStates,
+ TEST_CONCURRENT_DISPLAY.toIntArray()
+ )
+ }
+ deviceStateRepository =
+ DeviceStateRepositoryImpl(
+ mContext,
+ deviceStateManager,
+ TestScope(UnconfinedTestDispatcher()),
+ fakeExecutor
+ )
+
+ // It should register only after there are clients collecting the flow
+ verify(deviceStateManager, never()).registerCallback(any(), any())
+ }
+
+ @Test
+ fun folded_receivesFoldedState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(TEST_FOLDED)
+
+ assertThat(state()).isEqualTo(DeviceState.FOLDED)
+ }
+
+ @Test
+ fun halfFolded_receivesHalfFoldedState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(TEST_HALF_FOLDED)
+
+ assertThat(state()).isEqualTo(DeviceState.HALF_FOLDED)
+ }
+
+ @Test
+ fun unfolded_receivesUnfoldedState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(TEST_UNFOLDED)
+
+ assertThat(state()).isEqualTo(DeviceState.UNFOLDED)
+ }
+
+ @Test
+ fun rearDisplay_receivesRearDisplayState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(TEST_REAR_DISPLAY)
+
+ assertThat(state()).isEqualTo(DeviceState.REAR_DISPLAY)
+ }
+
+ @Test
+ fun concurrentDisplay_receivesConcurrentDisplayState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(TEST_CONCURRENT_DISPLAY)
+
+ assertThat(state()).isEqualTo(DeviceState.CONCURRENT_DISPLAY)
+ }
+
+ @Test
+ fun unknownState_receivesUnknownState() =
+ testScope.runTest {
+ val state = displayState()
+
+ deviceStateManagerListener.value.onStateChanged(123456)
+
+ assertThat(state()).isEqualTo(DeviceState.UNKNOWN)
+ }
+
+ private fun TestScope.displayState(): FlowValue<DeviceState?> {
+ val flowValue = collectLastValue(deviceStateRepository.state)
+ verify(deviceStateManager)
+ .registerCallback(
+ any(),
+ deviceStateManagerListener.capture(),
+ )
+ return flowValue
+ }
+
+ private fun Int.toIntArray() = listOf(this).toIntArray()
+
+ private companion object {
+ // Used to fake the ids in the test. Note that there is no guarantees different devices will
+ // have the same ids (that's why the ones in this test start from 41)
+ const val TEST_FOLDED = 41
+ const val TEST_HALF_FOLDED = 42
+ const val TEST_UNFOLDED = 43
+ const val TEST_REAR_DISPLAY = 44
+ const val TEST_CONCURRENT_DISPLAY = 45
+ }
+}
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 1f18705..42b0f50 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
@@ -28,6 +28,9 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.CONCURRENT_DISPLAY
+import com.android.systemui.display.data.repository.FakeDeviceStateRepository
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.createPendingDisplay
import com.android.systemui.display.data.repository.display
@@ -59,11 +62,13 @@
private val fakeDisplayRepository = FakeDisplayRepository()
private val fakeKeyguardRepository = FakeKeyguardRepository()
+ private val fakeDeviceStateRepository = FakeDeviceStateRepository()
private val connectedDisplayStateProvider: ConnectedDisplayInteractor =
ConnectedDisplayInteractorImpl(
virtualDeviceManager,
fakeKeyguardRepository,
fakeDisplayRepository,
+ fakeDeviceStateRepository,
UnconfinedTestDispatcher(),
)
private val testScope = TestScope(UnconfinedTestDispatcher())
@@ -283,6 +288,44 @@
assertThat(pendingDisplay).isNull()
}
+ @Test
+ fun concurrentDisplaysInProgress_started_returnsTrue() =
+ testScope.runTest {
+ val concurrentDisplaysInProgress =
+ collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+ fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY)
+
+ assertThat(concurrentDisplaysInProgress()).isTrue()
+ }
+
+ @Test
+ fun concurrentDisplaysInProgress_stopped_returnsFalse() =
+ testScope.runTest {
+ val concurrentDisplaysInProgress =
+ collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+ fakeDeviceStateRepository.emit(CONCURRENT_DISPLAY)
+ fakeDeviceStateRepository.emit(DeviceStateRepository.DeviceState.UNKNOWN)
+
+ assertThat(concurrentDisplaysInProgress()).isFalse()
+ }
+
+ @Test
+ fun concurrentDisplaysInProgress_otherStates_returnsFalse() =
+ testScope.runTest {
+ val concurrentDisplaysInProgress =
+ collectLastValue(connectedDisplayStateProvider.concurrentDisplaysInProgress)
+
+ DeviceStateRepository.DeviceState.entries
+ .filter { it != CONCURRENT_DISPLAY }
+ .forEach { deviceState ->
+ fakeDeviceStateRepository.emit(deviceState)
+
+ assertThat(concurrentDisplaysInProgress()).isFalse()
+ }
+ }
+
private fun TestScope.lastValue(): FlowValue<State?> =
collectLastValue(connectedDisplayStateProvider.connectedDisplayState)
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 bbc63f2..ae84df5 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
@@ -21,7 +21,6 @@
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.privacy.PrivacyItemController
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.mockito.any
@@ -107,5 +106,7 @@
get() = flow
override val pendingDisplay: Flow<PendingDisplay?>
get() = MutableSharedFlow<PendingDisplay>()
+ override val concurrentDisplaysInProgress: Flow<Boolean>
+ get() = TODO("Not yet implemented")
}
}
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 da6c28a..7deee5a 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
@@ -321,5 +321,7 @@
get() = TODO("Not yet implemented")
override val pendingDisplay: Flow<PendingDisplay?>
get() = TODO("Not yet implemented")
+ override val concurrentDisplaysInProgress: Flow<Boolean>
+ get() = TODO("Not yet implemented")
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.kt
new file mode 100644
index 0000000..5f6dc6e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDeviceStateRepository.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.display.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fake [DeviceStateRepository] implementation for testing. */
+class FakeDeviceStateRepository : DeviceStateRepository {
+ private val flow = MutableStateFlow(DeviceStateRepository.DeviceState.UNKNOWN)
+
+ /** Emits [value] as [displays] flow value. */
+ suspend fun emit(value: DeviceStateRepository.DeviceState) = flow.emit(value)
+
+ override val state: StateFlow<DeviceStateRepository.DeviceState>
+ get() = flow
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 2902932..468b7c9 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -41,6 +41,17 @@
android.content.IntentFilter
android.content.UriMatcher
+android.content.pm.PackageInfo
+android.content.pm.ApplicationInfo
+android.content.pm.PackageItemInfo
+android.content.pm.ComponentInfo
+android.content.pm.ActivityInfo
+android.content.pm.ServiceInfo
+android.content.pm.PathPermission
+android.content.pm.ProviderInfo
+android.content.pm.ResolveInfo
+android.content.pm.Signature
+
android.database.AbstractCursor
android.database.CharArrayBuffer
android.database.ContentObservable
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 258820a..77a5e3d 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -43,7 +43,9 @@
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener;
+import android.app.usage.Flags;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetManagerInternal;
@@ -83,6 +85,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -3815,14 +3818,27 @@
final SparseArray<String> uid2PackageName = new SparseArray<String>();
uid2PackageName.put(providerId.uid, packageName);
mAppOpsManagerInternal.updateAppWidgetVisibility(uid2PackageName, true);
- mUsageStatsManagerInternal.reportEvent(packageName,
- UserHandle.getUserId(providerId.uid), UsageEvents.Event.USER_INTERACTION);
+ reportWidgetInteractionEvent(packageName, UserHandle.getUserId(providerId.uid),
+ "tap");
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
+ private void reportWidgetInteractionEvent(@NonNull String packageName, @UserIdInt int userId,
+ @NonNull String action) {
+ if (Flags.userInteractionTypeApi()) {
+ PersistableBundle extras = new PersistableBundle();
+ extras.putString(UsageStatsManager.EXTRA_EVENT_CATEGORY, "android.appwidget");
+ extras.putString(UsageStatsManager.EXTRA_EVENT_ACTION, action);
+ mUsageStatsManagerInternal.reportUserInteractionEvent(packageName, userId, extras);
+ } else {
+ mUsageStatsManagerInternal.reportEvent(packageName, userId,
+ UsageEvents.Event.USER_INTERACTION);
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 382ee6e..4bb9f4f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -103,7 +103,7 @@
// will be half the full timeout).
//
// The pre-watchdog event is similar to a full watchdog except it does not crash system server.
- private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 3;
+ private static final int PRE_WATCHDOG_TIMEOUT_RATIO = 4;
// These are temporally ordered: larger values as lateness increases
static final int COMPLETED = 0;
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index e3aa161..a313bcf 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1745,8 +1745,8 @@
@Override
public boolean setSaturationLevel(int level) {
- final boolean hasTransformsPermission = getContext()
- .checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
+ final boolean hasTransformsPermission = getContext().checkCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS)
== PackageManager.PERMISSION_GRANTED;
final boolean hasLegacyPermission = getContext()
.checkCallingPermission(Manifest.permission.CONTROL_DISPLAY_SATURATION)
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 98f627c..b700785 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1188,23 +1188,6 @@
}
/**
- * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to the system user
- * only.
- */
- private final class ImmsBroadcastReceiverForSystemUser extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_USER_ADDED.equals(action)
- || Intent.ACTION_USER_REMOVED.equals(action)) {
- updateCurrentProfileIds();
- } else {
- Slog.w(TAG, "Unexpected intent " + intent);
- }
- }
- }
-
- /**
* {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users.
*/
private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver {
@@ -1611,9 +1594,16 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
// Called on ActivityManager thread.
+ SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
.sendToTarget();
}
+
+ @Override
+ public void onUserStarting(TargetUser user) {
+ // Called on ActivityManager thread.
+ SecureSettingsWrapper.onUserStarting(user.getUserIdentifier());
+ }
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1665,6 +1655,7 @@
@Nullable InputMethodBindingController bindingControllerForTesting) {
mContext = context;
mRes = context.getResources();
+ SecureSettingsWrapper.onStart(mContext);
// TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
// additional subtypes in switchUserOnHandlerLocked().
final ServiceThread thread =
@@ -1700,7 +1691,6 @@
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(mContext, mMethodMap, userId, !mSystemReady);
- updateCurrentProfileIds();
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
@@ -1818,7 +1808,6 @@
final boolean useCopyOnWriteSettings =
!mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
- updateCurrentProfileIds();
// Additional subtypes should be reset when the user is changed
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1869,12 +1858,6 @@
}
}
- void updateCurrentProfileIds() {
- mSettings.setCurrentProfileIds(
- mUserManagerInternal.getProfileIds(mSettings.getCurrentUserId(),
- false /* enabledOnly */));
- }
-
/**
* TODO(b/32343335): The entire systemRunning() method needs to be revisited.
*/
@@ -1921,12 +1904,6 @@
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
mSettingsObserver.registerContentObserverLocked(currentUserId);
- final IntentFilter broadcastFilterForSystemUser = new IntentFilter();
- broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED);
- broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(),
- broadcastFilterForSystemUser);
-
final IntentFilter broadcastFilterForAllUsers = new IntentFilter();
broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(),
@@ -3788,8 +3765,14 @@
mVisibilityStateComputer.mShowForced = false;
}
- // cross-profile access is always allowed here to allow profile-switching.
- if (!mSettings.isCurrentProfile(userId)) {
+ final int currentUserId = mSettings.getCurrentUserId();
+ if (userId != currentUserId) {
+ if (ArrayUtils.contains(
+ mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+ // cross-profile access is always allowed here to allow profile-switching.
+ scheduleSwitchUserTaskLocked(userId, cs.mClient);
+ return InputBindResult.USER_SWITCHING;
+ }
Slog.w(TAG, "A background user is requesting window. Hiding IME.");
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
@@ -3799,11 +3782,6 @@
return InputBindResult.INVALID_USER;
}
- if (userId != mSettings.getCurrentUserId()) {
- scheduleSwitchUserTaskLocked(userId, cs.mClient);
- return InputBindResult.USER_SWITCHING;
- }
-
final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index c661c86..b4338f6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -21,7 +21,6 @@
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -34,7 +33,6 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.IntArray;
import android.util.Pair;
import android.util.Printer;
@@ -51,7 +49,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@@ -211,36 +208,15 @@
*/
@UserHandleAware
public static class InputMethodSettings {
- private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
-
- private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
@NonNull
private Context mUserAwareContext;
- private ContentResolver mResolver;
private final ArrayMap<String, InputMethodInfo> mMethodMap;
- /**
- * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
- */
- private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
-
- private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
- static {
- Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
- }
-
- private static final UserManagerInternal sUserManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
-
private boolean mCopyOnWrite = false;
@NonNull
private String mEnabledInputMethodsStrCache = "";
@UserIdInt
private int mCurrentUserId;
- private int[] mCurrentProfileIds = new int[0];
private static void buildEnabledInputMethodsSettingString(
StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -281,7 +257,6 @@
mUserAwareContext = context.getUserId() == userId
? context
: context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
- mResolver = mUserAwareContext.getContentResolver();
}
InputMethodSettings(@NonNull Context context,
@@ -305,79 +280,38 @@
Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
}
if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
- mCopyOnWriteDataStore.clear();
mEnabledInputMethodsStrCache = "";
- // TODO: mCurrentProfileIds should be cleared here.
}
if (mUserAwareContext.getUserId() != userId) {
initContentWithUserContext(mUserAwareContext, userId);
}
mCurrentUserId = userId;
mCopyOnWrite = copyOnWrite;
- // TODO: mCurrentProfileIds should be updated here.
}
private void putString(@NonNull String key, @Nullable String str) {
- if (mCopyOnWrite) {
- mCopyOnWriteDataStore.put(key, str);
- } else {
- final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
- ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
- Settings.Secure.putStringForUser(mResolver, key, str, userId);
- }
+ SecureSettingsWrapper.putString(key, str, mCurrentUserId);
}
@Nullable
private String getString(@NonNull String key, @Nullable String defaultValue) {
- final String result;
- if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
- result = mCopyOnWriteDataStore.get(key);
- } else {
- result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
- }
- return result != null ? result : defaultValue;
+ return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
}
private void putInt(String key, int value) {
- if (mCopyOnWrite) {
- mCopyOnWriteDataStore.put(key, String.valueOf(value));
- } else {
- final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
- ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
- Settings.Secure.putIntForUser(mResolver, key, value, userId);
- }
+ SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
}
private int getInt(String key, int defaultValue) {
- if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
- final String result = mCopyOnWriteDataStore.get(key);
- return result != null ? Integer.parseInt(result) : defaultValue;
- }
- return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
+ return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
}
private void putBoolean(String key, boolean value) {
- putInt(key, value ? 1 : 0);
+ SecureSettingsWrapper.putBoolean(key, value, mCurrentUserId);
}
private boolean getBoolean(String key, boolean defaultValue) {
- return getInt(key, defaultValue ? 1 : 0) == 1;
- }
-
- public void setCurrentProfileIds(int[] currentProfileIds) {
- synchronized (this) {
- mCurrentProfileIds = currentProfileIds;
- }
- }
-
- public boolean isCurrentProfile(int userId) {
- synchronized (this) {
- if (userId == mCurrentUserId) return true;
- for (int i = 0; i < mCurrentProfileIds.length; i++) {
- if (userId == mCurrentProfileIds[i]) return true;
- }
- return false;
- }
+ return SecureSettingsWrapper.getBoolean(key, defaultValue, mCurrentUserId);
}
ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
@@ -428,8 +362,8 @@
List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
- mInputMethodSplitter,
- mSubtypeSplitter);
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR),
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
}
List<String> getEnabledInputMethodNames() {
@@ -700,16 +634,20 @@
if (TextUtils.isEmpty(subtypeHistoryStr)) {
return imsList;
}
- mInputMethodSplitter.setString(subtypeHistoryStr);
- while (mInputMethodSplitter.hasNext()) {
- String nextImsStr = mInputMethodSplitter.next();
- mSubtypeSplitter.setString(nextImsStr);
- if (mSubtypeSplitter.hasNext()) {
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ inputMethodSplitter.setString(subtypeHistoryStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
String subtypeId = NOT_A_SUBTYPE_ID_STR;
// The first element is ime id.
- String imeId = mSubtypeSplitter.next();
- while (mSubtypeSplitter.hasNext()) {
- subtypeId = mSubtypeSplitter.next();
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeId = subtypeSplitter.next();
break;
}
imsList.add(new Pair<>(imeId, subtypeId));
@@ -950,7 +888,6 @@
public void dumpLocked(final Printer pw, final String prefix) {
pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
- pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
}
@@ -1029,9 +966,7 @@
static List<String> getEnabledInputMethodIdsForFiltering(@NonNull Context context,
@UserIdInt int userId) {
final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
- Settings.Secure.getStringForUser(
- context.getContentResolver(),
- Settings.Secure.ENABLED_INPUT_METHODS,
+ SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null,
userId));
if (enabledInputMethodsStr == null) {
return List.of();
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
new file mode 100644
index 0000000..559b625
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.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 com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManagerInternal;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+/**
+ * A thread-safe utility class to encapsulate accesses to {@link Settings.Secure} that may need a
+ * special handling for direct-boot support.
+ *
+ * <p>Any changes made until the user storage is unlocked are non-persistent and will be reset
+ * to the persistent value when the user storage is unlocked.</p>
+ */
+final class SecureSettingsWrapper {
+ @Nullable
+ private static volatile ContentResolver sContentResolver = null;
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private SecureSettingsWrapper() {
+ }
+
+ private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
+ }
+
+ @AnyThread
+ @UserIdInt
+ private static int getUserIdForClonedSettings(@NonNull String key, @UserIdInt int userId) {
+ return CLONE_TO_MANAGED_PROFILE.contains(key)
+ ? LocalServices.getService(UserManagerInternal.class).getProfileParentId(userId)
+ : userId;
+ }
+
+ private interface ReaderWriter {
+ @AnyThread
+ void putString(@NonNull String key, @Nullable String value);
+
+ @AnyThread
+ @Nullable
+ String getString(@NonNull String key, @Nullable String defaultValue);
+
+ @AnyThread
+ void putInt(String key, int value);
+
+ @AnyThread
+ int getInt(String key, int defaultValue);
+ }
+
+ private static class UnlockedUserImpl implements ReaderWriter {
+ @UserIdInt
+ private final int mUserId;
+
+ private final ContentResolver mContentResolver;
+
+ UnlockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) {
+ mUserId = userId;
+ mContentResolver = contentResolver;
+ }
+
+ @AnyThread
+ @Override
+ public void putString(String key, String value) {
+ final int userId = getUserIdForClonedSettings(key, mUserId);
+ Settings.Secure.putStringForUser(mContentResolver, key, value, userId);
+ }
+
+ @AnyThread
+ @Nullable
+ @Override
+ public String getString(String key, String defaultValue) {
+ final String result = Settings.Secure.getStringForUser(mContentResolver, key, mUserId);
+ return result != null ? result : defaultValue;
+ }
+
+ @AnyThread
+ @Override
+ public void putInt(String key, int value) {
+ final int userId = getUserIdForClonedSettings(key, mUserId);
+ Settings.Secure.putIntForUser(mContentResolver, key, value, userId);
+ }
+
+ @AnyThread
+ @Override
+ public int getInt(String key, int defaultValue) {
+ return Settings.Secure.getIntForUser(mContentResolver, key, defaultValue, mUserId);
+ }
+ }
+
+ /**
+ * For users whose storages are not unlocked yet, we do not want to update IME related Secure
+ * Settings. Any write operations will be forwarded to
+ * {@link LockedUserImpl#mNonPersistentKeyValues} so that we can return the volatile data until
+ * the user storage is unlocked.
+ */
+ private static final class LockedUserImpl extends UnlockedUserImpl {
+ @GuardedBy("mNonPersistentKeyValues")
+ private final ArrayMap<String, String> mNonPersistentKeyValues = new ArrayMap<>();
+
+ LockedUserImpl(@UserIdInt int userId, @NonNull ContentResolver contentResolver) {
+ super(userId, contentResolver);
+ }
+
+ @AnyThread
+ @Override
+ public void putString(String key, String value) {
+ synchronized (mNonPersistentKeyValues) {
+ mNonPersistentKeyValues.put(key, value);
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ @Override
+ public String getString(String key, String defaultValue) {
+ synchronized (mNonPersistentKeyValues) {
+ if (mNonPersistentKeyValues.containsKey(key)) {
+ final String result = mNonPersistentKeyValues.get(key);
+ return result != null ? result : defaultValue;
+ }
+ return super.getString(key, defaultValue);
+ }
+ }
+
+ @AnyThread
+ @Override
+ public void putInt(String key, int value) {
+ synchronized (mNonPersistentKeyValues) {
+ mNonPersistentKeyValues.put(key, String.valueOf(value));
+ }
+ }
+
+ @AnyThread
+ @Override
+ public int getInt(String key, int defaultValue) {
+ synchronized (mNonPersistentKeyValues) {
+ if (mNonPersistentKeyValues.containsKey(key)) {
+ final String result = mNonPersistentKeyValues.get(key);
+ return result != null ? Integer.parseInt(result) : defaultValue;
+ }
+ return super.getInt(key, defaultValue);
+ }
+ }
+ }
+
+ @GuardedBy("sUserMap")
+ @NonNull
+ private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
+
+ private static final ReaderWriter NOOP = new ReaderWriter() {
+ @Override
+ public void putString(String key, String str) {
+ }
+
+ @Override
+ public String getString(String key, String defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ }
+
+ @Override
+ public int getInt(String key, int defaultValue) {
+ return defaultValue;
+ }
+ };
+
+ private static ReaderWriter createImpl(@NonNull UserManagerInternal userManagerInternal,
+ @UserIdInt int userId) {
+ return userManagerInternal.isUserUnlockingOrUnlocked(userId)
+ ? new UnlockedUserImpl(userId, sContentResolver)
+ : new LockedUserImpl(userId, sContentResolver);
+ }
+
+ @NonNull
+ @AnyThread
+ private static ReaderWriter putOrGet(@UserIdInt int userId,
+ @NonNull ReaderWriter readerWriter) {
+ final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
+ synchronized (sUserMap) {
+ final ReaderWriter current = sUserMap.get(userId);
+ if (current == null) {
+ sUserMap.put(userId, readerWriter);
+ return readerWriter;
+ }
+ // Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
+ if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
+ sUserMap.put(userId, readerWriter);
+ return readerWriter;
+ }
+ return current;
+ }
+ }
+
+ @NonNull
+ @AnyThread
+ private static ReaderWriter get(@UserIdInt int userId) {
+ synchronized (sUserMap) {
+ final ReaderWriter readerWriter = sUserMap.get(userId);
+ if (readerWriter != null) {
+ return readerWriter;
+ }
+ }
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ if (!userManagerInternal.exists(userId)) {
+ return NOOP;
+ }
+ return putOrGet(userId, createImpl(userManagerInternal, userId));
+ }
+
+ /**
+ * Called when {@link InputMethodManagerService} is starting.
+ *
+ * @param context the {@link Context} to be used.
+ */
+ @AnyThread
+ static void onStart(@NonNull Context context) {
+ sContentResolver = context.getContentResolver();
+
+ final int userId = LocalServices.getService(ActivityManagerInternal.class)
+ .getCurrentUserId();
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ putOrGet(userId, createImpl(userManagerInternal, userId));
+
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ synchronized (sUserMap) {
+ sUserMap.remove(userId);
+ }
+ }
+ }
+ );
+ }
+
+ /**
+ * Called when a user is starting.
+ *
+ * @param userId the ID of the user who is starting.
+ */
+ @AnyThread
+ static void onUserStarting(@UserIdInt int userId) {
+ putOrGet(userId, createImpl(LocalServices.getService(UserManagerInternal.class), userId));
+ }
+
+ /**
+ * Called when a user is being unlocked.
+ *
+ * @param userId the ID of the user whose storage is being unlocked.
+ */
+ @AnyThread
+ static void onUserUnlocking(@UserIdInt int userId) {
+ final ReaderWriter readerWriter = new UnlockedUserImpl(userId, sContentResolver);
+ putOrGet(userId, readerWriter);
+ }
+
+ /**
+ * Put the given string {@code value} to {@code key}.
+ *
+ * @param key a secure settings key.
+ * @param value a secure settings value.
+ * @param userId the ID of a user whose secure settings will be updated.
+ * @see Settings.Secure#putStringForUser(ContentResolver, String, String, int)
+ */
+ @AnyThread
+ static void putString(String key, String value, @UserIdInt int userId) {
+ get(userId).putString(key, value);
+ }
+
+ /**
+ * Get a string value with the given {@code key}
+ *
+ * @param key a secure settings key.
+ * @param defaultValue the default value when the value is not found.
+ * @param userId the ID of a user whose secure settings will be updated.
+ * @return The string value if it is found. {@code defaultValue} otherwise.
+ * @see Settings.Secure#getStringForUser(ContentResolver, String, int)
+ */
+ @AnyThread
+ @Nullable
+ static String getString(String key, String defaultValue, @UserIdInt int userId) {
+ return get(userId).getString(key, defaultValue);
+ }
+
+ /**
+ * Put the given integer {@code value} to {@code key}.
+ *
+ * @param key a secure settings key.
+ * @param value a secure settings value.
+ * @param userId the ID of a user whose secure settings will be updated.
+ * @see Settings.Secure#putIntForUser(ContentResolver, String, int, int)
+ */
+ @AnyThread
+ static void putInt(String key, int value, @UserIdInt int userId) {
+ get(userId).putInt(key, value);
+ }
+
+ /**
+ * Get an integer value with the given {@code key}
+ *
+ * @param key a secure settings key.
+ * @param defaultValue the default value when the value is not found.
+ * @param userId the ID of a user whose secure settings will be updated.
+ * @return The integer value if it is found. {@code defaultValue} otherwise.
+c */
+ @AnyThread
+ static int getInt(String key, int defaultValue, @UserIdInt int userId) {
+ return get(userId).getInt(key, defaultValue);
+ }
+
+ /**
+ * Put the given boolean {@code value} to {@code key}.
+ *
+ * @param key a secure settings key.
+ * @param value a secure settings value.
+ * @param userId the ID of a user whose secure settings will be updated.
+ */
+ @AnyThread
+ static void putBoolean(String key, boolean value, @UserIdInt int userId) {
+ get(userId).putInt(key, value ? 1 : 0);
+ }
+
+ /**
+ * Get a boolean value with the given {@code key}
+ *
+ * @param key a secure settings key.
+ * @param defaultValue the default value when the value is not found.
+ * @param userId the ID of a user whose secure settings will be updated.
+ * @return The boolean value if it is found. {@code defaultValue} otherwise.
+ */
+ @AnyThread
+ static boolean getBoolean(String key, boolean defaultValue, @UserIdInt int userId) {
+ return get(userId).getInt(key, defaultValue ? 1 : 0) == 1;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
new file mode 100644
index 0000000..9fdeda4
--- /dev/null
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -0,0 +1,71 @@
+/*
+ * 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.server.notification;
+
+import android.app.UiModeManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.hardware.display.ColorDisplayManager;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenDeviceEffects;
+
+/** Default implementation for {@link DeviceEffectsApplier}. */
+class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
+
+ private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN =
+ "DefaultDeviceEffectsApplier:SuppressAmbientDisplay";
+ private static final int SATURATION_LEVEL_GRAYSCALE = 0;
+ private static final int SATURATION_LEVEL_FULL_COLOR = 100;
+ private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f;
+ private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f;
+
+ private final ColorDisplayManager mColorDisplayManager;
+ private final PowerManager mPowerManager;
+ private final UiModeManager mUiModeManager;
+ private final WallpaperManager mWallpaperManager;
+
+ DefaultDeviceEffectsApplier(Context context) {
+ mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ mWallpaperManager = context.getSystemService(WallpaperManager.class);
+ }
+
+ @Override
+ public void apply(ZenDeviceEffects effects) {
+ Binder.withCleanCallingIdentity(() -> {
+ mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN,
+ effects.shouldSuppressAmbientDisplay());
+
+ if (mColorDisplayManager != null) {
+ mColorDisplayManager.setSaturationLevel(
+ effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE
+ : SATURATION_LEVEL_FULL_COLOR);
+ }
+
+ if (mWallpaperManager != null) {
+ mWallpaperManager.setWallpaperDimAmount(
+ effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED
+ : WALLPAPER_DIM_AMOUNT_NORMAL);
+ }
+
+ // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off.
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3c6887c1..02845fb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2941,6 +2941,12 @@
registerDeviceConfigChange();
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
+
+ if (android.app.Flags.modesApi()) {
+ // Cannot be done earlier, as some services aren't ready until this point.
+ mZenModeHelper.setDeviceEffectsApplier(
+ new DefaultDeviceEffectsApplier(getContext()));
+ }
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
} else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 5c37eea..218519f 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,8 +27,8 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
-import android.annotation.IntDef;
import android.annotation.DrawableRes;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -76,6 +76,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
+import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
@@ -175,6 +176,8 @@
@VisibleForTesting protected int mZenMode;
@VisibleForTesting protected NotificationManager.Policy mConsolidatedPolicy;
+ @GuardedBy("mConfigLock")
+ private ZenDeviceEffects mConsolidatedDeviceEffects = new ZenDeviceEffects.Builder().build();
private int mUser = UserHandle.USER_SYSTEM;
private final Object mConfigLock = new Object();
@@ -182,6 +185,8 @@
@VisibleForTesting protected ZenModeConfig mConfig;
@VisibleForTesting protected AudioManagerInternal mAudioManager;
protected PackageManager mPm;
+ @GuardedBy("mConfigLock")
+ private DeviceEffectsApplier mDeviceEffectsApplier;
private long mSuppressedEffects;
public static final long SUPPRESSED_EFFECT_NOTIFICATIONS = 1;
@@ -189,7 +194,7 @@
public static final long SUPPRESSED_EFFECT_ALL = SUPPRESSED_EFFECT_CALLS
| SUPPRESSED_EFFECT_NOTIFICATIONS;
- @VisibleForTesting protected boolean mIsBootComplete;
+ @VisibleForTesting protected boolean mIsSystemServicesReady;
private String[] mPriorityOnlyDndExemptPackages;
@@ -285,10 +290,33 @@
mPm = mContext.getPackageManager();
mHandler.postMetricsTimer();
cleanUpZenRules();
- mIsBootComplete = true;
+ mIsSystemServicesReady = true;
showZenUpgradeNotification(mZenMode);
}
+ /**
+ * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects.
+ *
+ * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig}
+ * that includes activated rules), they will be applied immediately.
+ */
+ void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ ZenDeviceEffects consolidatedDeviceEffects;
+ synchronized (mConfigLock) {
+ if (mDeviceEffectsApplier != null) {
+ throw new IllegalStateException("Already set up a DeviceEffectsApplier!");
+ }
+ mDeviceEffectsApplier = deviceEffectsApplier;
+ consolidatedDeviceEffects = mConsolidatedDeviceEffects;
+ }
+ if (consolidatedDeviceEffects.hasEffects()) {
+ applyConsolidatedDeviceEffects();
+ }
+ }
+
public void onUserSwitched(int user) {
loadConfigForUser(user, "onUserSwitched");
}
@@ -1349,7 +1377,7 @@
mConfig = config;
dispatchOnConfigChanged();
- updateConsolidatedPolicy(reason);
+ updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
}
final String val = Integer.toString(config.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
@@ -1398,7 +1426,7 @@
ZenLog.traceSetZenMode(zen, reason);
mZenMode = zen;
setZenModeSetting(mZenMode);
- updateConsolidatedPolicy(reason);
+ updateAndApplyConsolidatedPolicyAndDeviceEffects(reason);
boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || (
zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
&& policyHashBefore != mConsolidatedPolicy.hashCode()));
@@ -1459,25 +1487,56 @@
}
}
- private void updateConsolidatedPolicy(String reason) {
+ private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) {
synchronized (mConfigLock) {
if (mConfig == null) return;
ZenPolicy policy = new ZenPolicy();
+ ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder();
if (mConfig.manualRule != null) {
applyCustomPolicy(policy, mConfig.manualRule);
+ if (Flags.modesApi()) {
+ deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects);
+ }
}
for (ZenRule automaticRule : mConfig.automaticRules.values()) {
if (automaticRule.isAutomaticActive()) {
applyCustomPolicy(policy, automaticRule);
+ if (Flags.modesApi()) {
+ deviceEffectsBuilder.add(automaticRule.zenDeviceEffects);
+ }
}
}
+
Policy newPolicy = mConfig.toNotificationPolicy(policy);
if (!Objects.equals(mConsolidatedPolicy, newPolicy)) {
mConsolidatedPolicy = newPolicy;
dispatchOnConsolidatedPolicyChanged();
ZenLog.traceSetConsolidatedZenPolicy(mConsolidatedPolicy, reason);
}
+
+ if (Flags.modesApi()) {
+ ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
+ if (!deviceEffects.equals(mConsolidatedDeviceEffects)) {
+ mConsolidatedDeviceEffects = deviceEffects;
+ mHandler.postApplyDeviceEffects();
+ }
+ }
+ }
+ }
+
+ private void applyConsolidatedDeviceEffects() {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ DeviceEffectsApplier applier;
+ ZenDeviceEffects effects;
+ synchronized (mConfigLock) {
+ applier = mDeviceEffectsApplier;
+ effects = mConsolidatedDeviceEffects;
+ }
+ if (applier != null) {
+ applier.apply(effects);
}
}
@@ -1893,7 +1952,7 @@
private void showZenUpgradeNotification(int zen) {
final boolean isWatch = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WATCH);
- final boolean showNotification = mIsBootComplete
+ final boolean showNotification = mIsSystemServicesReady
&& zen != Global.ZEN_MODE_OFF
&& !isWatch
&& Settings.Secure.getInt(mContext.getContentResolver(),
@@ -2067,6 +2126,7 @@
private static final int MSG_DISPATCH = 1;
private static final int MSG_METRICS = 2;
private static final int MSG_RINGER_AUDIO = 5;
+ private static final int MSG_APPLY_EFFECTS = 6;
private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
@@ -2089,6 +2149,11 @@
sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
}
+ private void postApplyDeviceEffects() {
+ removeMessages(MSG_APPLY_EFFECTS);
+ sendEmptyMessage(MSG_APPLY_EFFECTS);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -2101,6 +2166,10 @@
case MSG_RINGER_AUDIO:
boolean shouldApplyToRinger = (boolean) msg.obj;
updateRingerAndAudio(shouldApplyToRinger);
+ break;
+ case MSG_APPLY_EFFECTS:
+ applyConsolidatedDeviceEffects();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index f77d7898..d9c8ec6 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -317,11 +317,11 @@
return 1;
}
final String overlayPackageName = "com.android.shell";
- FabricatedOverlay.Builder overlayBuilder = new FabricatedOverlay.Builder(
- overlayPackageName, name, targetPackage)
- .setTargetOverlayable(targetOverlayable);
+ FabricatedOverlay overlay = new FabricatedOverlay(name, targetPackage);
+ overlay.setTargetOverlayable(targetOverlayable);
+ overlay.setOwningPackage(overlayPackageName);
if (filename != null) {
- int result = addOverlayValuesFromXml(overlayBuilder, targetPackage, filename);
+ int result = addOverlayValuesFromXml(overlay, targetPackage, filename);
if (result != 0) {
return result;
}
@@ -329,18 +329,18 @@
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
final String strData = String.join(" ", peekRemainingArgs());
- if (addOverlayValue(overlayBuilder, resourceName, typeStr, strData, config) != 0) {
+ if (addOverlayValue(overlay, resourceName, typeStr, strData, config) != 0) {
return 1;
}
}
mInterface.commit(new OverlayManagerTransaction.Builder()
- .registerFabricatedOverlay(overlayBuilder.build()).build());
+ .registerFabricatedOverlay(overlay).build());
return 0;
}
private int addOverlayValuesFromXml(
- FabricatedOverlay.Builder overlayBuilder, String targetPackage, String filename) {
+ FabricatedOverlay overlay, String targetPackage, String filename) {
final PrintWriter err = getErrPrintWriter();
File file = new File(filename);
if (!file.exists()) {
@@ -388,7 +388,7 @@
return 1;
}
String config = parser.getAttributeValue(null, "config");
- if (addOverlayValue(overlayBuilder, targetPackage + ':' + target,
+ if (addOverlayValue(overlay, targetPackage + ':' + target,
overlayType, value, config) != 0) {
return 1;
}
@@ -405,8 +405,8 @@
return 0;
}
- private int addOverlayValue(FabricatedOverlay.Builder overlayBuilder,
- String resourceName, String typeString, String valueString, String configuration) {
+ private int addOverlayValue(FabricatedOverlay overlay, String resourceName, String typeString,
+ String valueString, String configuration) {
final int type;
typeString = typeString.toLowerCase(Locale.getDefault());
if (TYPE_MAP.containsKey(typeString)) {
@@ -419,10 +419,14 @@
}
}
if (type == TypedValue.TYPE_STRING) {
- overlayBuilder.setResourceValue(resourceName, type, valueString, configuration);
+ overlay.setResourceValue(resourceName, type, valueString, configuration);
} else if (type < 0) {
ParcelFileDescriptor pfd = openFileForSystem(valueString, "r");
- overlayBuilder.setResourceValue(resourceName, pfd, configuration);
+ if (valueString.endsWith(".9.png")) {
+ overlay.setNinePatchResourceValue(resourceName, pfd, configuration);
+ } else {
+ overlay.setResourceValue(resourceName, pfd, configuration);
+ }
} else {
final int intData;
if (valueString.startsWith("0x")) {
@@ -430,7 +434,7 @@
} else {
intData = Integer.parseUnsignedInt(valueString);
}
- overlayBuilder.setResourceValue(resourceName, type, intData, configuration);
+ overlay.setResourceValue(resourceName, type, intData, configuration);
}
return 0;
}
diff --git a/services/core/java/com/android/server/pdb/TEST_MAPPING b/services/core/java/com/android/server/pdb/TEST_MAPPING
new file mode 100644
index 0000000..1aa8601
--- /dev/null
+++ b/services/core/java/com/android/server/pdb/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pdb.PersistentDataBlockServiceTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index e088d9a..1485b96 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -40,6 +40,7 @@
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
+import static com.android.window.flags.Flags.multiCrop;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -93,7 +94,6 @@
import android.os.SELinux;
import android.os.ShellCallback;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -1516,8 +1516,7 @@
mColorsChangedListeners = new SparseArray<>();
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
mWallpaperCropper);
- mIsMultiCropEnabled =
- SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
+ mIsMultiCropEnabled = multiCrop();
LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index b8b102f..91f45a7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -76,6 +76,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE;
@@ -125,7 +126,6 @@
import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import android.Manifest;
import android.annotation.IntDef;
@@ -1261,10 +1261,10 @@
true /*validateIncomingUser*/);
}
- static boolean isSdkSandboxActivity(Context context, Intent intent) {
+ static boolean isSdkSandboxActivityIntent(Context context, Intent intent) {
return intent != null
&& (sandboxActivitySdkBasedContext()
- ? SdkSandboxActivityAuthority.isSdkSandboxActivity(context, intent)
+ ? SdkSandboxActivityAuthority.isSdkSandboxActivityIntent(context, intent)
: intent.isSandboxActivity(context));
}
@@ -1278,7 +1278,7 @@
assertPackageMatchesCallingUid(callingPackage);
enforceNotIsolatedCaller("startActivityAsUser");
- if (isSdkSandboxActivity(mContext, intent)) {
+ if (isSdkSandboxActivityIntent(mContext, intent)) {
SdkSandboxManagerLocal sdkSandboxManagerLocal = LocalManagerRegistry.getManager(
SdkSandboxManagerLocal.class);
sdkSandboxManagerLocal.enforceAllowedToHostSandboxedActivity(
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index a21b9b4..4a479aa 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1085,7 +1085,8 @@
// Remove the process record so it won't be considered as alive.
mService.mProcessNames.remove(wpc.mName, wpc.mUid);
mService.mProcessMap.remove(wpc.getPid());
- } else if (ActivityTaskManagerService.isSdkSandboxActivity(mService.mContext, r.intent)) {
+ } else if (ActivityTaskManagerService.isSdkSandboxActivityIntent(
+ mService.mContext, r.intent)) {
Slog.e(TAG, "Abort sandbox activity launching as no sandbox process to host it.");
r.finishIfPossible("No sandbox process for the activity", false /* oomAdj */);
r.launchFailed = true;
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index be7b855..c5902c9 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -395,7 +395,8 @@
*
* @return false if unable to predict what will happen
*/
- private static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
+ @VisibleForTesting
+ static boolean getAnimatablePrevActivities(@NonNull Task currentTask,
@NonNull ActivityRecord currentActivity,
@NonNull ArrayList<ActivityRecord> outPrevActivities) {
if (currentActivity.mAtmService
@@ -413,45 +414,86 @@
// Searching previous
final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing,
currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/);
- if (prevActivity == null) {
- // No previous activity in this task, can still predict if previous task exists.
- return true;
- }
- if (currentTask.getActivity((above) -> !above.finishing, currentActivity,
- false /*includeBoundary*/, false /*traverseTopToBottom*/) != null) {
- // another activity is above this activity, don't know what will happen
- return false;
- }
final TaskFragment currTF = currentActivity.getTaskFragment();
- final TaskFragment prevTF = prevActivity.getTaskFragment();
- if (currTF != prevTF && prevTF != null) {
- final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
- if (prevTFAdjacent != null) {
- if (prevTFAdjacent == currTF) {
- outPrevActivities.clear();
- // No more activity in task, so it can predict if previous task exists.
- // Otherwise, unable to predict what will happen when app receive
- // back key, skip animation.
- return currentTask.getActivity((below) -> !below.finishing, prevActivity,
+ if (currTF != null && currTF.asTask() == null) {
+ // The currentActivity is embedded, search for the candidate previous activities.
+ if (prevActivity != null && currTF.hasChild(prevActivity)) {
+ // PrevActivity is under the same task fragment, that's it.
+ outPrevActivities.add(prevActivity);
+ return true;
+ }
+ if (currTF.getAdjacentTaskFragment() != null) {
+ // The two TFs are adjacent (visually displayed side-by-side), search if any
+ // activity below the lowest one
+ // If companion, those two TF will be closed together.
+ if (currTF.getCompanionTaskFragment() != null) {
+ final WindowContainer commonParent = currTF.getParent();
+ final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment();
+ final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF)
+ < commonParent.mChildren.indexOf(adjacentTF)
+ ? currTF : adjacentTF;
+ final ActivityRecord lowerActivity = lowerTF.getTopNonFinishingActivity();
+ // TODO (b/274997067) close currTF + companionTF, open next activities if any.
+ // Allow to predict next task if no more activity in task. Or return previous
+ // activities for cross-activity animation.
+ return currentTask.getActivity((below) -> !below.finishing, lowerActivity,
false /*includeBoundary*/, true /*traverseTopToBottom*/) == null;
- } else {
- final ActivityRecord prevActivityAdjacent =
- prevTFAdjacent.getTopNonFinishingActivity();
- if (prevActivityAdjacent != null) {
- outPrevActivities.add(prevActivityAdjacent);
- } else {
- // Don't know what will happen.
- outPrevActivities.clear();
- return false;
- }
}
+ // Unable to predict if no companion, it can only close current activity and make
+ // prev Activity full screened.
+ return false;
+ } else if (currTF.getCompanionTaskFragment() != null) {
+ // TF is isStacked, search bottom activity from companion TF.
+ //
+ // Sample hierarchy: search for underPrevious if any.
+ // Current TF
+ // Companion TF (bottomActivityInCompanion)
+ // Bottom Activity not inside companion TF (underPrevious)
+ final TaskFragment companionTF = currTF.getCompanionTaskFragment();
+ // find bottom activity in Companion TF.
+ final ActivityRecord bottomActivityInCompanion = companionTF.getActivity(
+ (below) -> !below.finishing, false /* traverseTopToBottom */);
+ final ActivityRecord underPrevious = currentTask.getActivity(
+ (below) -> !below.finishing, bottomActivityInCompanion,
+ false /*includeBoundary*/, true /*traverseTopToBottom*/);
+ if (underPrevious != null) {
+ outPrevActivities.add(underPrevious);
+ addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities);
+ }
+ return true;
}
}
+
+ if (prevActivity == null) {
+ // No previous activity in this Task nor TaskFragment, it can still predict if previous
+ // task exists.
+ return true;
+ }
+ // Add possible adjacent activity if prevActivity is embedded
+ addPreviousAdjacentActivityIfExist(prevActivity, outPrevActivities);
outPrevActivities.add(prevActivity);
return true;
}
+ private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity,
+ @NonNull ArrayList<ActivityRecord> outPrevActivities) {
+ final TaskFragment prevTF = prevActivity.getTaskFragment();
+ if (prevTF == null || prevTF.asTask() != null) {
+ return;
+ }
+
+ final TaskFragment prevTFAdjacent = prevTF.getAdjacentTaskFragment();
+ if (prevTFAdjacent == null || prevTFAdjacent.asTask() != null) {
+ return;
+ }
+ final ActivityRecord prevActivityAdjacent =
+ prevTFAdjacent.getTopNonFinishingActivity();
+ if (prevActivityAdjacent != null) {
+ outPrevActivities.add(prevActivityAdjacent);
+ }
+ }
+
private static void findAdjacentActivityIfExist(@NonNull ActivityRecord mainActivity,
@NonNull ArrayList<ActivityRecord> outList) {
final TaskFragment mainTF = mainActivity.getTaskFragment();
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f51bf7f..0006bd2 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -420,7 +420,7 @@
@Override
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
ActivityRecord boundary) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return null;
}
return super.getActivity(callback, traverseTopToBottom, boundary);
@@ -428,23 +428,39 @@
@Override
Task getTask(Predicate<Task> callback, boolean traverseTopToBottom) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return null;
}
return super.getTask(callback, traverseTopToBottom);
}
@Override
+ Task getRootTask(Predicate<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS) {
+ return null;
+ }
+ return super.getRootTask(callback, traverseTopToBottom);
+ }
+
+ @Override
boolean forAllActivities(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return false;
}
return super.forAllActivities(callback, traverseTopToBottom);
}
@Override
+ void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS) {
+ return;
+ }
+ super.forAllActivities(callback, traverseTopToBottom);
+ }
+
+ @Override
boolean forAllRootTasks(Predicate<Task> callback, boolean traverseTopToBottom) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return false;
}
return super.forAllRootTasks(callback, traverseTopToBottom);
@@ -452,7 +468,7 @@
@Override
boolean forAllTasks(Predicate<Task> callback) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return false;
}
return super.forAllTasks(callback);
@@ -460,13 +476,29 @@
@Override
boolean forAllLeafTasks(Predicate<Task> callback) {
- if (mType == Type.ABOVE_TASKS || mType == Type.BELOW_TASKS) {
+ if (mType == Type.ABOVE_TASKS) {
return false;
}
return super.forAllLeafTasks(callback);
}
@Override
+ void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
+ if (mType == Type.ABOVE_TASKS) {
+ return;
+ }
+ super.forAllLeafTasks(callback, traverseTopToBottom);
+ }
+
+ @Override
+ boolean forAllLeafTaskFragments(Predicate<TaskFragment> callback) {
+ if (mType == Type.ABOVE_TASKS) {
+ return false;
+ }
+ return super.forAllLeafTaskFragments(callback);
+ }
+
+ @Override
void forAllDisplayAreas(Consumer<DisplayArea> callback) {
super.forAllDisplayAreas(callback);
callback.accept(this);
diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
deleted file mode 100644
index e82dc37..0000000
--- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * 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.server.wm;
-
-import static android.graphics.Matrix.MSCALE_X;
-import static android.graphics.Matrix.MSCALE_Y;
-import static android.graphics.Matrix.MSKEW_X;
-import static android.graphics.Matrix.MSKEW_Y;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Size;
-import android.view.InputWindowHandle;
-import android.window.ITrustedPresentationListener;
-import android.window.TrustedPresentationThresholds;
-import android.window.WindowInfosListener;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.wm.utils.RegionUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Optional;
-
-/**
- * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class
- * also takes care of cleaning up listeners when the remote process dies.
- */
-public class TrustedPresentationListenerController {
-
- // Should only be accessed by the posting to the handler
- private class Listeners {
- private final class ListenerDeathRecipient implements IBinder.DeathRecipient {
- IBinder mListenerBinder;
- int mInstances;
-
- ListenerDeathRecipient(IBinder listenerBinder) {
- mListenerBinder = listenerBinder;
- mInstances = 0;
- try {
- mListenerBinder.linkToDeath(this, 0);
- } catch (RemoteException ignore) {
- }
- }
-
- void addInstance() {
- mInstances++;
- }
-
- // return true if there are no instances alive
- boolean removeInstance() {
- mInstances--;
- if (mInstances > 0) {
- return false;
- }
- mListenerBinder.unlinkToDeath(this, 0);
- return true;
- }
-
- public void binderDied() {
- mHandler.post(() -> {
- mUniqueListeners.remove(mListenerBinder);
- removeListeners(mListenerBinder, Optional.empty());
- });
- }
- }
-
- // tracks binder deaths for cleanup
- ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>();
- ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners =
- new ArrayMap<>();
-
- void register(IBinder window, ITrustedPresentationListener listener,
- TrustedPresentationThresholds thresholds, int id) {
- var listenersForWindow = mWindowToListeners.computeIfAbsent(window,
- iBinder -> new ArrayList<>());
- listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener));
-
- // register death listener
- var listenerBinder = listener.asBinder();
- var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder,
- ListenerDeathRecipient::new);
- deathRecipient.addInstance();
- }
-
- void unregister(ITrustedPresentationListener trustedPresentationListener, int id) {
- var listenerBinder = trustedPresentationListener.asBinder();
- var deathRecipient = mUniqueListeners.get(listenerBinder);
- if (deathRecipient == null) {
- ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find"
- + " deathRecipient for %s with id=%d", trustedPresentationListener, id);
- return;
- }
-
- if (deathRecipient.removeInstance()) {
- mUniqueListeners.remove(listenerBinder);
- }
- removeListeners(listenerBinder, Optional.of(id));
- }
-
- boolean isEmpty() {
- return mWindowToListeners.isEmpty();
- }
-
- ArrayList<TrustedPresentationInfo> get(IBinder windowToken) {
- return mWindowToListeners.get(windowToken);
- }
-
- private void removeListeners(IBinder listenerBinder, Optional<Integer> id) {
- for (int i = mWindowToListeners.size() - 1; i >= 0; i--) {
- var listeners = mWindowToListeners.valueAt(i);
- for (int j = listeners.size() - 1; j >= 0; j--) {
- var listener = listeners.get(j);
- if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty()
- || listener.mId == id.get())) {
- listeners.remove(j);
- }
- }
- if (listeners.isEmpty()) {
- mWindowToListeners.removeAt(i);
- }
- }
- }
- }
-
- private final Object mHandlerThreadLock = new Object();
- private HandlerThread mHandlerThread;
- private Handler mHandler;
-
- private WindowInfosListener mWindowInfosListener;
-
- Listeners mRegisteredListeners = new Listeners();
-
- private InputWindowHandle[] mLastWindowHandles;
-
- private final Object mIgnoredWindowTokensLock = new Object();
-
- private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>();
-
- private void startHandlerThreadIfNeeded() {
- synchronized (mHandlerThreadLock) {
- if (mHandler == null) {
- mHandlerThread = new HandlerThread("WindowInfosListenerForTpl");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
- }
- }
- }
-
- void addIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.add(token);
- }
- }
-
- void removeIgnoredWindowTokens(IBinder token) {
- synchronized (mIgnoredWindowTokensLock) {
- mIgnoredWindowTokens.remove(token);
- }
- }
-
- void registerListener(IBinder window, ITrustedPresentationListener listener,
- TrustedPresentationThresholds thresholds, int id) {
- startHandlerThreadIfNeeded();
- mHandler.post(() -> {
- ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s",
- listener, id, window, thresholds);
-
- mRegisteredListeners.register(window, listener, thresholds, id);
- registerWindowInfosListener();
- // Update the initial state for the new registered listener
- computeTpl(mLastWindowHandles);
- });
- }
-
- void unregisterListener(ITrustedPresentationListener listener, int id) {
- startHandlerThreadIfNeeded();
- mHandler.post(() -> {
- ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d",
- listener, id);
-
- mRegisteredListeners.unregister(listener, id);
- if (mRegisteredListeners.isEmpty()) {
- unregisterWindowInfosListener();
- }
- });
- }
-
- void dump(PrintWriter pw) {
- final String innerPrefix = " ";
- pw.println("TrustedPresentationListenerController:");
- pw.println(innerPrefix + "Active unique listeners ("
- + mRegisteredListeners.mUniqueListeners.size() + "):");
- for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) {
- pw.println(
- innerPrefix + " window=" + mRegisteredListeners.mWindowToListeners.keyAt(i));
- final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i);
- for (int j = 0; j < listeners.size(); j++) {
- final var listener = listeners.get(j);
- pw.println(innerPrefix + innerPrefix + " listener=" + listener.mListener.asBinder()
- + " id=" + listener.mId
- + " thresholds=" + listener.mThresholds);
- }
- }
- }
-
- private void registerWindowInfosListener() {
- if (mWindowInfosListener != null) {
- return;
- }
-
- mWindowInfosListener = new WindowInfosListener() {
- @Override
- public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
- DisplayInfo[] displayInfos) {
- mHandler.post(() -> computeTpl(windowHandles));
- }
- };
- mLastWindowHandles = mWindowInfosListener.register().first;
- }
-
- private void unregisterWindowInfosListener() {
- if (mWindowInfosListener == null) {
- return;
- }
-
- mWindowInfosListener.unregister();
- mWindowInfosListener = null;
- mLastWindowHandles = null;
- }
-
- private void computeTpl(InputWindowHandle[] windowHandles) {
- mLastWindowHandles = windowHandles;
- if (mLastWindowHandles == null || mLastWindowHandles.length == 0
- || mRegisteredListeners.isEmpty()) {
- return;
- }
-
- Rect tmpRect = new Rect();
- Matrix tmpInverseMatrix = new Matrix();
- float[] tmpMatrix = new float[9];
- Region coveredRegionsAbove = new Region();
- long currTimeMs = System.currentTimeMillis();
- ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length);
-
- ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates =
- new ArrayMap<>();
- ArraySet<IBinder> ignoredWindowTokens;
- synchronized (mIgnoredWindowTokensLock) {
- ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens);
- }
- for (var windowHandle : mLastWindowHandles) {
- if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) {
- ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name);
- continue;
- }
- tmpRect.set(windowHandle.frame);
- var listeners = mRegisteredListeners.get(windowHandle.getWindowToken());
- if (listeners != null) {
- Region region = new Region();
- region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE);
- windowHandle.transform.invert(tmpInverseMatrix);
- tmpInverseMatrix.getValues(tmpMatrix);
- float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X]
- + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]);
- float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y]
- + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]);
-
- float fractionRendered = computeFractionRendered(region, new RectF(tmpRect),
- windowHandle.contentSize,
- scaleX, scaleY);
-
- checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha,
- currTimeMs);
- }
-
- coveredRegionsAbove.op(tmpRect, Region.Op.UNION);
- ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s",
- windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove);
- }
-
- for (int i = 0; i < listenerUpdates.size(); i++) {
- var updates = listenerUpdates.valueAt(i);
- var listener = listenerUpdates.keyAt(i);
- try {
- listener.onTrustedPresentationChanged(updates.first.toArray(),
- updates.second.toArray());
- } catch (RemoteException ignore) {
- }
- }
- }
-
- private void addListenerUpdate(
- ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
- ITrustedPresentationListener listener, int id, boolean presentationState) {
- var updates = listenerUpdates.get(listener);
- if (updates == null) {
- updates = new Pair<>(new IntArray(), new IntArray());
- listenerUpdates.put(listener, updates);
- }
- if (presentationState) {
- updates.first.add(id);
- } else {
- updates.second.add(id);
- }
- }
-
-
- private void checkIfInThreshold(
- ArrayList<TrustedPresentationInfo> listeners,
- ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates,
- float fractionRendered, float alpha, long currTimeMs) {
- ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d",
- fractionRendered, alpha, currTimeMs);
- for (int i = 0; i < listeners.size(); i++) {
- var trustedPresentationInfo = listeners.get(i);
- var listener = trustedPresentationInfo.mListener;
- boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState;
- boolean newState =
- (alpha >= trustedPresentationInfo.mThresholds.mMinAlpha) && (fractionRendered
- >= trustedPresentationInfo.mThresholds.mMinFractionRendered);
- trustedPresentationInfo.mLastComputedTrustedPresentationState = newState;
-
- ProtoLog.v(WM_DEBUG_TPL,
- "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f "
- + "minFractionRendered=%f",
- lastState, newState, alpha, trustedPresentationInfo.mThresholds.mMinAlpha,
- fractionRendered, trustedPresentationInfo.mThresholds.mMinFractionRendered);
-
- if (lastState && !newState) {
- // We were in the trusted presentation state, but now we left it,
- // emit the callback if needed
- if (trustedPresentationInfo.mLastReportedTrustedPresentationState) {
- trustedPresentationInfo.mLastReportedTrustedPresentationState = false;
- addListenerUpdate(listenerUpdates, listener,
- trustedPresentationInfo.mId, /*presentationState*/ false);
- ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d",
- listener, trustedPresentationInfo.mId);
- }
- // Reset the timer
- trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1;
- } else if (!lastState && newState) {
- // We were not in the trusted presentation state, but we entered it, begin the timer
- // and make sure this gets called at least once more!
- trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs;
- mHandler.postDelayed(() -> {
- computeTpl(mLastWindowHandles);
- }, (long) (trustedPresentationInfo.mThresholds.mStabilityRequirementMs * 1.5));
- }
-
- // Has the timer elapsed, but we are still in the state? Emit a callback if needed
- if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && (
- currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime
- > trustedPresentationInfo.mThresholds.mStabilityRequirementMs)) {
- trustedPresentationInfo.mLastReportedTrustedPresentationState = true;
- addListenerUpdate(listenerUpdates, listener,
- trustedPresentationInfo.mId, /*presentationState*/ true);
- ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d",
- listener, trustedPresentationInfo.mId);
- }
- }
- }
-
- private float computeFractionRendered(Region visibleRegion, RectF screenBounds,
- Size contentSize,
- float sx, float sy) {
- ProtoLog.v(WM_DEBUG_TPL,
- "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s "
- + "scale=%f,%f",
- visibleRegion, screenBounds, contentSize, sx, sy);
-
- if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) {
- return -1;
- }
- if (screenBounds.width() == 0 || screenBounds.height() == 0) {
- return -1;
- }
-
- float fractionRendered = Math.min(sx * sy, 1.0f);
- ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered);
-
- float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth();
- float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight();
- fractionRendered *= boundsOverSourceW * boundsOverSourceH;
- ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered);
- // Compute the size of all the rects since they may be disconnected.
- float[] visibleSize = new float[1];
- RegionUtils.forEachRect(visibleRegion, rect -> {
- float size = rect.width() * rect.height();
- visibleSize[0] += size;
- });
-
- fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height());
- return fractionRendered;
- }
-
- private static class TrustedPresentationInfo {
- boolean mLastComputedTrustedPresentationState = false;
- boolean mLastReportedTrustedPresentationState = false;
- long mEnteredTrustedPresentationStateTime = -1;
- final TrustedPresentationThresholds mThresholds;
-
- final ITrustedPresentationListener mListener;
- final int mId;
-
- private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id,
- ITrustedPresentationListener listener) {
- mThresholds = thresholds;
- mId = id;
- mListener = listener;
- checkValid(thresholds);
- }
-
- private void checkValid(TrustedPresentationThresholds thresholds) {
- if (thresholds.mMinAlpha <= 0 || thresholds.mMinFractionRendered <= 0
- || thresholds.mStabilityRequirementMs < 1) {
- throw new IllegalArgumentException(
- "TrustedPresentationThresholds values are invalid");
- }
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0c57036..0d2c94d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,11 +303,9 @@
import android.window.ClientWindowFrames;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
-import android.window.ITrustedPresentationListener;
import android.window.ScreenCapture;
import android.window.SystemPerformanceHinter;
import android.window.TaskSnapshot;
-import android.window.TrustedPresentationThresholds;
import android.window.WindowContainerToken;
import android.window.WindowContextInfo;
@@ -766,9 +764,6 @@
private final SurfaceSyncGroupController mSurfaceSyncGroupController =
new SurfaceSyncGroupController();
- final TrustedPresentationListenerController mTrustedPresentationListenerController =
- new TrustedPresentationListenerController();
-
@VisibleForTesting
final class SettingsObserver extends ContentObserver {
private final Uri mDisplayInversionEnabledUri =
@@ -7176,7 +7171,6 @@
pw.println(separator);
}
mSystemPerformanceHinter.dump(pw, "");
- mTrustedPresentationListenerController.dump(pw);
}
}
@@ -9777,17 +9771,4 @@
Binder.restoreCallingIdentity(origId);
}
}
-
- @Override
- public void registerTrustedPresentationListener(IBinder window,
- ITrustedPresentationListener listener,
- TrustedPresentationThresholds thresholds, int id) {
- mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id);
- }
-
- @Override
- public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener,
- int id) {
- mTrustedPresentationListenerController.unregisterListener(listener, id);
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7bc7e2c..e1f1f66 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1189,11 +1189,6 @@
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow);
parentWindow.addChild(this, sWindowSubLayerComparator);
}
-
- if (token.mRoundedCornerOverlay) {
- mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens(
- getWindowToken());
- }
}
@Override
@@ -2398,9 +2393,6 @@
}
mWmService.postWindowRemoveCleanupLocked(this);
-
- mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens(
- getWindowToken());
}
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
new file mode 100644
index 0000000..5febd02
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.server.notification;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.app.UiModeManager;
+import android.app.WallpaperManager;
+import android.hardware.display.ColorDisplayManager;
+import android.os.PowerManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.service.notification.ZenDeviceEffects;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class DefaultDeviceEffectsApplierTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private TestableContext mContext;
+ private DefaultDeviceEffectsApplier mApplier;
+ @Mock PowerManager mPowerManager;
+ @Mock ColorDisplayManager mColorDisplayManager;
+ @Mock UiModeManager mUiModeManager;
+ @Mock WallpaperManager mWallpaperManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = new TestableContext(InstrumentationRegistry.getContext(), null);
+ mContext.addMockSystemService(PowerManager.class, mPowerManager);
+ mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
+ mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
+ mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager);
+
+ mApplier = new DefaultDeviceEffectsApplier(mContext);
+ }
+
+ @Test
+ public void apply_appliesEffects() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build();
+ mApplier.apply(effects);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+ verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :)
+ }
+
+ @Test
+ public void apply_removesEffects() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+ mApplier.apply(noEffects);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verify(mColorDisplayManager).setSaturationLevel(eq(100));
+ verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
+ verifyZeroInteractions(mUiModeManager);
+ }
+
+ @Test
+ public void apply_missingSomeServices_okay() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mContext.addMockSystemService(ColorDisplayManager.class, null);
+ mContext.addMockSystemService(WallpaperManager.class, null);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
+ .build();
+ mApplier.apply(effects);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ // (And no crash from missing services).
+ }
+}
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 b1fdec9..ef6fced 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -77,9 +77,9 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
@@ -112,6 +112,7 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
+import android.service.notification.DeviceEffectsApplier;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
@@ -188,8 +189,10 @@
.appendPath("test")
.build();
- private static final Condition CONDITION = new Condition(CONDITION_ID, "",
+ private static final Condition CONDITION_TRUE = new Condition(CONDITION_ID, "",
Condition.STATE_TRUE);
+ private static final Condition CONDITION_FALSE = new Condition(CONDITION_ID, "",
+ Condition.STATE_FALSE);
private static final String TRIGGER_DESC = "Every Night, 10pm to 6am";
private static final int TYPE = TYPE_BEDTIME;
private static final boolean ALLOW_MANUAL = true;
@@ -201,6 +204,7 @@
= NotificationManager.INTERRUPTION_FILTER_ALARMS;
private static final boolean ENABLED = true;
private static final int CREATION_TIME = 123;
+ private static final ZenDeviceEffects NO_EFFECTS = new ZenDeviceEffects.Builder().build();
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -212,6 +216,7 @@
private TestableLooper mTestableLooper;
private ZenModeHelper mZenModeHelper;
private ContentResolver mContentResolver;
+ @Mock DeviceEffectsApplier mDeviceEffectsApplier;
@Mock AppOpsManager mAppOps;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
ZenModeEventLoggerFake mZenModeEventLogger;
@@ -238,8 +243,9 @@
when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(
mResources);
- when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOps);
- when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
+ mContext.addMockSystemService(AppOpsManager.class, mAppOps);
+ mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
+
mConditionProviders = new ConditionProviders(mContext, new UserProfiles(),
AppGlobals.getPackageManager());
mConditionProviders.addSystemProvider(new CountdownConditionProvider());
@@ -609,7 +615,7 @@
// and we're setting zen mode on
Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1);
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsBootComplete = true;
+ mZenModeHelper.mIsSystemServicesReady = true;
mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0);
mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -624,7 +630,7 @@
// doesn't show upgrade notification if stored settings says don't show
Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 0);
- mZenModeHelper.mIsBootComplete = true;
+ mZenModeHelper.mIsSystemServicesReady = true;
mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -636,7 +642,7 @@
// doesn't show upgrade notification since zen was already updated
Settings.Secure.putInt(mContentResolver, Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0);
Settings.Secure.putInt(mContentResolver, Settings.Secure.ZEN_SETTINGS_UPDATED, 1);
- mZenModeHelper.mIsBootComplete = true;
+ mZenModeHelper.mIsSystemServicesReady = true;
mZenModeHelper.setZenModeSetting(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
verify(mNotificationManager, never()).notify(eq(ZenModeHelper.TAG),
@@ -3060,7 +3066,7 @@
rule.configurationActivity = CONFIG_ACTIVITY;
rule.component = OWNER;
rule.conditionId = CONDITION_ID;
- rule.condition = CONDITION;
+ rule.condition = CONDITION_TRUE;
rule.enabled = ENABLED;
rule.creationTime = 123;
rule.id = "id";
@@ -3350,6 +3356,84 @@
}
@Test
+ public void testDeviceEffects_applied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+ ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .setShouldDimWallpaper(true)
+ .build();
+ String ruleId = addRuleWithEffects(effects);
+ verify(mDeviceEffectsApplier, never()).apply(any());
+
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(effects));
+ }
+
+ @Test
+ public void testDeviceEffects_onDeactivateRule_applied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+ ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+ String ruleId = addRuleWithEffects(zde);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(eq(zde));
+
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false);
+ mTestableLooper.processAllMessages();
+
+ verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS));
+ }
+
+ @Test
+ public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+
+ ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+ String ruleId = addRuleWithEffects(zde);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier).apply(eq(zde));
+
+ // Now create and activate a second rule that doesn't add any more effects.
+ String secondRuleId = addRuleWithEffects(zde);
+ mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID,
+ false);
+ mTestableLooper.processAllMessages();
+
+ verifyNoMoreInteractions(mDeviceEffectsApplier);
+ }
+
+ @Test
+ public void testDeviceEffects_activeBeforeApplierProvided_appliedWhenProvided() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
+ String ruleId = addRuleWithEffects(zde);
+ verify(mDeviceEffectsApplier, never()).apply(any());
+
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false);
+ mTestableLooper.processAllMessages();
+ verify(mDeviceEffectsApplier, never()).apply(any());
+
+ mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
+ verify(mDeviceEffectsApplier).apply(eq(zde));
+ }
+
+ private String addRuleWithEffects(ZenDeviceEffects effects) {
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID)
+ .setDeviceEffects(effects)
+ .build();
+ return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP);
+ }
+
+ @Test
public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index a8d3fa1..c3074bb 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -99,7 +99,7 @@
android:theme="@style/WhiteBackgroundTheme"
android:exported="true"/>
- <activity android:name="com.android.server.wm.TrustedPresentationListenerTest$TestActivity"
+ <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity"
android:exported="true"
android:showWhenLocked="true"
android:turnScreenOn="true" />
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index afea811..4d4d397 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -242,6 +242,110 @@
}
@Test
+ public void backTypeCrossActivityInTaskFragment() {
+ final Task task = createTask(mDefaultDisplay);
+ final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+ final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+ final ArrayList<ActivityRecord> outPrevActivities = new ArrayList<>();
+
+ ActivityRecord prevAr = tf1.getTopMostActivity();
+ ActivityRecord topAr = tf2.getTopMostActivity();
+ boolean predictable;
+
+ // Stacked + no Companion => predict for previous activity.
+ // TF2
+ // TF1
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.contains(prevAr));
+ assertTrue(predictable);
+ outPrevActivities.clear();
+
+ // Stacked + companion => predict for previous task
+ tf2.setCompanionTaskFragment(tf1);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.isEmpty());
+ assertTrue(predictable);
+ tf2.setCompanionTaskFragment(null);
+
+ // Adjacent + no companion => unable to predict
+ // TF1 | TF2
+ tf1.setAdjacentTaskFragment(tf2);
+ tf2.setAdjacentTaskFragment(tf1);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.isEmpty());
+ assertFalse(predictable);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.isEmpty());
+ assertFalse(predictable);
+
+ // Adjacent + companion => predict for previous task
+ tf1.setCompanionTaskFragment(tf2);
+ tf2.setCompanionTaskFragment(tf1);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.isEmpty());
+ assertTrue(predictable);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.isEmpty());
+ assertTrue(predictable);
+ // reset
+ tf1.setAdjacentTaskFragment(null);
+ tf2.setAdjacentTaskFragment(null);
+ tf1.setCompanionTaskFragment(null);
+ tf2.setCompanionTaskFragment(null);
+
+ final TaskFragment tf3 = new TaskFragmentBuilder(mAtm)
+ .createActivityCount(2)
+ .setParentTask(task)
+ .build();
+ topAr = tf3.getTopMostActivity();
+ prevAr = tf3.getBottomMostActivity();
+ // Stacked => predict for previous activity.
+ // TF3
+ // TF2
+ // TF1
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.contains(prevAr));
+ assertTrue(predictable);
+ // reset
+ outPrevActivities.clear();
+
+ // Adjacent => predict for previous activity.
+ // TF2 | TF3
+ // TF1
+ tf2.setAdjacentTaskFragment(tf3);
+ tf3.setAdjacentTaskFragment(tf2);
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.contains(prevAr));
+ assertTrue(predictable);
+ // reset
+ outPrevActivities.clear();
+ tf2.setAdjacentTaskFragment(null);
+ tf3.setAdjacentTaskFragment(null);
+
+ final TaskFragment tf4 = createTaskFragmentWithActivity(task);
+ // Stacked + companion => predict for previous activity below companion.
+ // Tf4
+ // TF3
+ // TF2
+ // TF1
+ tf4.setCompanionTaskFragment(tf3);
+ tf3.setCompanionTaskFragment(tf4);
+ topAr = tf4.getTopMostActivity();
+ predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr,
+ outPrevActivities);
+ assertTrue(outPrevActivities.contains(tf2.getTopMostActivity()));
+ assertTrue(predictable);
+ }
+
+ @Test
public void backTypeDialogCloseWhenBackFromDialog() {
DialogCloseTestCase testCase = createTopTaskWithActivityAndDialog();
IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
new file mode 100644
index 0000000..c5dd447
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.server.wm;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.CtsWindowInfoUtils;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.TrustedPresentationThresholds;
+
+import androidx.annotation.GuardedBy;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import java.util.function.Consumer;
+
+/**
+ * TODO (b/287076178): Move these tests to
+ * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public
+ */
+@Presubmit
+public class TrustedPresentationCallbackTest {
+ private static final String TAG = "TrustedPresentationCallbackTest";
+ private static final int STABILITY_REQUIREMENT_MS = 500;
+ private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
+
+ private static final float FRACTION_VISIBLE = 0.1f;
+
+ private final Object mResultsLock = new Object();
+ @GuardedBy("mResultsLock")
+ private boolean mResult;
+ @GuardedBy("mResultsLock")
+ private boolean mReceivedResults;
+
+ @Rule
+ public TestName mName = new TestName();
+
+ @Rule
+ public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
+ TestActivity.class);
+
+ private TestActivity mActivity;
+
+ @Before
+ public void setup() {
+ mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+ }
+
+ @After
+ public void tearDown() {
+ CommonUtils.waitUntilActivityRemoved(mActivity);
+ }
+
+ @Test
+ public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
+ TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+ 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+ Runnable::run, inTrustedPresentationState -> {
+ synchronized (mResultsLock) {
+ mResult = inTrustedPresentationState;
+ mReceivedResults = true;
+ mResultsLock.notify();
+ }
+ });
+ t.apply();
+ synchronized (mResultsLock) {
+ assertResults();
+ }
+ }
+
+ @Test
+ public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
+ TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
+ 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
+ Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
+ synchronized (mResultsLock) {
+ mResult = inTrustedPresentationState;
+ mReceivedResults = true;
+ mResultsLock.notify();
+ }
+ };
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
+ Runnable::run, trustedPresentationCallback);
+ t.apply();
+
+ synchronized (mResultsLock) {
+ if (!mReceivedResults) {
+ mResultsLock.wait(WAIT_TIME_MS);
+ }
+ assertResults();
+ // reset the state
+ mReceivedResults = false;
+ }
+
+ mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
+ trustedPresentationCallback);
+ t.apply();
+
+ synchronized (mResultsLock) {
+ if (!mReceivedResults) {
+ mResultsLock.wait(WAIT_TIME_MS);
+ }
+ // Ensure we waited the full time and never received a notify on the result from the
+ // callback.
+ assertFalse("Should never have received a callback", mReceivedResults);
+ // results shouldn't have changed.
+ assertTrue(mResult);
+ }
+ }
+
+ @GuardedBy("mResultsLock")
+ private void assertResults() throws InterruptedException {
+ mResultsLock.wait(WAIT_TIME_MS);
+
+ if (!mReceivedResults) {
+ CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
+ }
+ // Make sure we received the results and not just timed out
+ assertTrue("Timed out waiting for results", mReceivedResults);
+ assertTrue(mResult);
+ }
+
+ public static class TestActivity extends Activity {
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java
deleted file mode 100644
index 96b66bf..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationListenerTest.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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.server.wm;
-
-import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
-import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.CtsWindowInfoUtils;
-import android.util.AndroidRuntimeException;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.window.TrustedPresentationThresholds;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
-import com.android.server.wm.utils.CommonUtils;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * TODO (b/287076178): Move these tests to
- * {@link android.view.surfacecontrol.cts.TrustedPresentationListenerTest} when API is made public
- */
-@Presubmit
-public class TrustedPresentationListenerTest {
- private static final String TAG = "TrustedPresentationListenerTest";
- private static final int STABILITY_REQUIREMENT_MS = 500;
- private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L;
-
- private static final float FRACTION_VISIBLE = 0.1f;
-
- private final List<Boolean> mResults = Collections.synchronizedList(new ArrayList<>());
- private CountDownLatch mReceivedResults = new CountDownLatch(1);
-
- private TrustedPresentationThresholds mThresholds = new TrustedPresentationThresholds(
- 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
-
- @Rule
- public TestName mName = new TestName();
-
- @Rule
- public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
- TestActivity.class);
-
- private TestActivity mActivity;
-
- private SurfaceControlViewHost.SurfacePackage mSurfacePackage = null;
-
- @Before
- public void setup() {
- mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
- mDefaultListener = new Listener(mReceivedResults);
- }
-
- @After
- public void tearDown() {
- if (mSurfacePackage != null) {
- new SurfaceControl.Transaction().remove(mSurfacePackage.getSurfaceControl()).apply(
- true);
- mSurfacePackage.release();
- }
- CommonUtils.waitUntilActivityRemoved(mActivity);
-
- }
-
- private class Listener implements Consumer<Boolean> {
- final CountDownLatch mLatch;
-
- Listener(CountDownLatch latch) {
- mLatch = latch;
- }
-
- @Override
- public void accept(Boolean inTrustedPresentationState) {
- Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
- mResults.add(inTrustedPresentationState);
- mLatch.countDown();
- }
- }
-
- private Consumer<Boolean> mDefaultListener;
-
- @Test
- public void testAddTrustedPresentationListenerOnWindow() {
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
- windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run,
- mDefaultListener);
- assertResults(List.of(true));
- }
-
- @Test
- public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
- windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds, Runnable::run,
- mDefaultListener);
- assertResults(List.of(true));
- // reset the latch
- mReceivedResults = new CountDownLatch(1);
-
- windowManager.unregisterTrustedPresentationListener(mDefaultListener);
- mReceivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
- // Ensure we waited the full time and never received a notify on the result from the
- // callback.
- assertEquals("Should never have received a callback", mReceivedResults.getCount(), 1);
- // results shouldn't have changed.
- assertEquals(mResults, List.of(true));
- }
-
- @Test
- public void testRemovingUnknownListenerIsANoop() {
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
- assertNotNull(windowManager);
- windowManager.unregisterTrustedPresentationListener(mDefaultListener);
- }
-
- @Test
- public void testAddDuplicateListenerThrowsException() {
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
- assertNotNull(windowManager);
- windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
- Runnable::run, mDefaultListener);
- assertThrows(AndroidRuntimeException.class,
- () -> windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
- Runnable::run, mDefaultListener));
- }
-
- @Test
- public void testAddDuplicateThresholds() {
- mReceivedResults = new CountDownLatch(2);
- mDefaultListener = new Listener(mReceivedResults);
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
- windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
- Runnable::run, mDefaultListener);
-
- Consumer<Boolean> mNewListener = new Listener(mReceivedResults);
-
- windowManager.registerTrustedPresentationListener(
- mActivity.getWindow().getDecorView().getWindowToken(), mThresholds,
- Runnable::run, mNewListener);
- assertResults(List.of(true, true));
- }
-
- private void waitForViewAttach(View view) {
- final CountDownLatch viewAttached = new CountDownLatch(1);
- view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- viewAttached.countDown();
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
-
- }
- });
- try {
- viewAttached.await(2000, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- if (!wait(viewAttached, 2000 /* waitTimeMs */)) {
- fail("Couldn't attach view=" + view);
- }
- }
-
- @Test
- public void testAddListenerToScvh() {
- WindowManager windowManager = mActivity.getSystemService(WindowManager.class);
-
- var embeddedView = new View(mActivity);
- mActivityRule.getScenario().onActivity(activity -> {
- var attachedSurfaceControl =
- mActivity.getWindow().getDecorView().getRootSurfaceControl();
- var scvh = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
- attachedSurfaceControl.getHostToken());
- mSurfacePackage = scvh.getSurfacePackage();
- scvh.setView(embeddedView, mActivity.getWindow().getDecorView().getWidth(),
- mActivity.getWindow().getDecorView().getHeight());
- attachedSurfaceControl.buildReparentTransaction(
- mSurfacePackage.getSurfaceControl());
- });
-
- waitForViewAttach(embeddedView);
- windowManager.registerTrustedPresentationListener(embeddedView.getWindowToken(),
- mThresholds,
- Runnable::run, mDefaultListener);
-
- assertResults(List.of(true));
- }
-
- private boolean wait(CountDownLatch latch, long waitTimeMs) {
- while (true) {
- long now = SystemClock.uptimeMillis();
- try {
- return latch.await(waitTimeMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- long elapsedTime = SystemClock.uptimeMillis() - now;
- waitTimeMs = Math.max(0, waitTimeMs - elapsedTime);
- }
- }
-
- }
-
- @GuardedBy("mResultsLock")
- private void assertResults(List<Boolean> results) {
- if (!wait(mReceivedResults, WAIT_TIME_MS)) {
- try {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
- } catch (InterruptedException e) {
- Log.d(TAG, "Couldn't dump windows", e);
- }
- Assert.fail("Timed out waiting for results mReceivedResults.count="
- + mReceivedResults.getCount() + "mReceivedResults=" + mReceivedResults);
- }
-
- // Make sure we received the results
- assertEquals(results.toArray(), mResults.toArray());
- }
-
- public static class TestActivity extends Activity {
- }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index b3eb285..8b44579 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -221,9 +221,7 @@
for (int line = 0; line < length / kDumpBytesPerLine; line++) {
StringBuilder sb = new StringBuilder();
for (int offset = 0; offset < kDumpBytesPerLine; offset++) {
- sb.append("0x")
- .append(String.format("0x%02X", mDescriptors[dataOffset++]))
- .append(" ");
+ sb.append(String.format("0x%02X", mDescriptors[dataOffset++])).append(" ");
}
pw.println(sb.toString());
}
@@ -231,9 +229,7 @@
// remainder
StringBuilder sb = new StringBuilder();
while (dataOffset < length) {
- sb.append("0x")
- .append(String.format("0x%02X", mDescriptors[dataOffset++]))
- .append(" ");
+ sb.append(String.format("0x%02X", mDescriptors[dataOffset++])).append(" ");
}
pw.println(sb.toString());
} else {
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
index e8389d19..0dce870 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotateSplitNoChangeTest.kt
@@ -43,8 +43,6 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class RotateSplitNoChangeTest(flicker: LegacyFlickerTest) : RotationTransition(flicker) {
-
- override val testApp = ActivityEmbeddingAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 1123c5b..f6d51f9 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -18,17 +18,14 @@
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
-import com.android.server.wm.flicker.BaseTest
+import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.setRotation
import org.junit.Test
/** Base class for app rotation tests */
-abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker) {
- protected abstract val testApp: StandardAppHelper
-
+abstract class RotationTransition(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
index baf204a..a117599d 100644
--- a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
index deb3cee..538abe8 100644
--- a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
index 34e25f7..79a1d6b 100644
--- a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ