Merge "Non-functionally refactor scanning logic" into main
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f97100b..83db4cb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1433,10 +1433,10 @@
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
synchronized (mLock) {
- // Exclude jobs scheduled on behalf of this app for now because SyncManager
+ // Exclude jobs scheduled on behalf of this app because SyncManager
// and other job proxy agents may not know to reschedule the job properly
// after force stop.
- // TODO(209852664): determine how to best handle syncs & other proxied jobs
+ // Proxied jobs will not be allowed to run if the source app is stopped.
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
/* includeSchedulingApp */ true, /* includeSourceApp */ false,
JobParameters.STOP_REASON_USER,
@@ -1448,7 +1448,9 @@
}
};
- private String getPackageName(Intent intent) {
+ /** Returns the package name stored in the intent's data. */
+ @Nullable
+ public static String getPackageName(Intent intent) {
Uri uri = intent.getData();
String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
return pkg;
@@ -5365,6 +5367,14 @@
}
pw.println();
+ pw.println("Aconfig flags:");
+ pw.increaseIndent();
+ pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
+ Flags.throwOnUnsupportedBiasUsage());
+ pw.println();
+ pw.decreaseIndent();
+ pw.println();
+
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
mJobRestrictions.get(i).dumpConstants(pw);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 293088d..c14efae 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -58,6 +58,8 @@
return cancelJob(pw);
case "monitor-battery":
return monitorBattery(pw);
+ case "get-aconfig-flag-state":
+ return getAconfigFlagState(pw);
case "get-battery-seq":
return getBatterySeq(pw);
case "get-battery-charging":
@@ -336,6 +338,28 @@
return 0;
}
+ private int getAconfigFlagState(PrintWriter pw) throws Exception {
+ checkPermission("get aconfig flag state");
+
+ final String flagName = getNextArgRequired();
+
+ switch (flagName) {
+ case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
+ pw.println(android.app.job.Flags.jobDebugInfoApis());
+ break;
+ case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
+ pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ break;
+ case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
+ pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
+ break;
+ default:
+ pw.println("Unknown flag: " + flagName);
+ break;
+ }
+ return 0;
+ }
+
private int getBatterySeq(PrintWriter pw) {
int seq = mInternal.getBatterySeq();
pw.println(seq);
@@ -693,6 +717,9 @@
pw.println(" monitor-battery [on|off]");
pw.println(" Control monitoring of all battery changes. Off by default. Turning");
pw.println(" on makes get-battery-seq useful.");
+ pw.println(" get-aconfig-flag-state FULL_FLAG_NAME");
+ pw.println(" Return the state of the specified aconfig flag, if known. The flag name");
+ pw.println(" must be fully qualified.");
pw.println(" get-battery-seq");
pw.println(" Return the last battery update sequence number that was received.");
pw.println(" get-battery-charging");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index cd3ba6b..4aadc90 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -17,18 +17,26 @@
package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.getPackageName;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
@@ -50,6 +58,8 @@
*
* - the uid-active boolean state expressed by the AppStateTracker. Jobs in 'active'
* uids are inherently eligible to run jobs regardless of the uid's standby bucket.
+ *
+ * - the app's stopped state
*/
public final class BackgroundJobsController extends StateController {
private static final String TAG = "JobScheduler.Background";
@@ -63,9 +73,48 @@
private final ActivityManagerInternal mActivityManagerInternal;
private final AppStateTrackerImpl mAppStateTracker;
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Boolean> mPackageStoppedState = new SparseArrayMap<>();
private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String pkgName = getPackageName(intent);
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final String action = intent.getAction();
+ if (pkgUid == -1) {
+ Slog.e(TAG, "Didn't get package UID in intent (" + action + ")");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Got " + action + " for " + pkgUid + "/" + pkgName);
+ }
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_RESTARTED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+ updateJobRestrictionsForUidLocked(pkgUid, false);
+ }
+ }
+ break;
+
+ case Intent.ACTION_PACKAGE_UNSTOPPED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.FALSE);
+ updateJobRestrictionsLocked(pkgUid, UNKNOWN);
+ }
+ }
+ break;
+ }
+ }
+ };
+
public BackgroundJobsController(JobSchedulerService service) {
super(service);
@@ -73,11 +122,18 @@
LocalServices.getService(ActivityManagerInternal.class));
mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
LocalServices.getService(AppStateTracker.class));
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
@Override
public void startTrackingLocked() {
mAppStateTracker.addListener(mForceAppStandbyListener);
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, filter, null, null);
}
@Override
@@ -99,11 +155,45 @@
}
@Override
+ public void onAppRemovedLocked(String packageName, int uid) {
+ mPackageStoppedState.delete(uid, packageName);
+ }
+
+ @Override
+ public void onUserRemovedLocked(int userId) {
+ for (int u = mPackageStoppedState.numMaps() - 1; u >= 0; --u) {
+ final int uid = mPackageStoppedState.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mPackageStoppedState.deleteAt(u);
+ }
+ }
+ }
+
+ @Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
+ pw.println("Aconfig flags:");
+ pw.increaseIndent();
+ pw.print(android.content.pm.Flags.FLAG_STAY_STOPPED,
+ android.content.pm.Flags.stayStopped());
+ pw.println();
+ pw.decreaseIndent();
+ pw.println();
+
mAppStateTracker.dump(pw);
pw.println();
+ pw.println("Stopped packages:");
+ pw.increaseIndent();
+ mPackageStoppedState.forEach((uid, pkgName, isStopped) -> {
+ pw.print(uid);
+ pw.print(":");
+ pw.print(pkgName);
+ pw.print("=");
+ pw.println(isStopped);
+ });
+ pw.println();
+
mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
final int uid = jobStatus.getSourceUid();
final String sourcePkg = jobStatus.getSourcePackageName();
@@ -205,14 +295,34 @@
}
}
+ private boolean isPackageStopped(String packageName, int uid) {
+ if (mPackageStoppedState.contains(uid, packageName)) {
+ return mPackageStoppedState.get(uid, packageName);
+ }
+ final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+ mPackageStoppedState.add(uid, packageName, isStopped);
+ return isStopped;
+ }
+
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, final long nowElapsed,
int activeState) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean isUserBgRestricted =
- !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
- && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName);
+ final boolean isSourcePkgStopped =
+ isPackageStopped(jobStatus.getSourcePackageName(), jobStatus.getSourceUid());
+ final boolean isCallingPkgStopped;
+ if (!jobStatus.isProxyJob()) {
+ isCallingPkgStopped = isSourcePkgStopped;
+ } else {
+ isCallingPkgStopped =
+ isPackageStopped(jobStatus.getCallingPackageName(), jobStatus.getUid());
+ }
+ final boolean isStopped = android.content.pm.Flags.stayStopped()
+ && (isCallingPkgStopped || isSourcePkgStopped);
+ final boolean isUserBgRestricted = isStopped
+ || (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
// If a job started with the foreground flag, it'll cause the UID to stay active
// and thus cause areJobsRestricted() to always return false, so if
// areJobsRestricted() returns false and the app is BG restricted and not TOP,
@@ -233,7 +343,8 @@
&& isUserBgRestricted
&& mService.getUidProcState(uid)
> ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- final boolean canRun = !shouldStopImmediately
+ // Don't let jobs (including proxied jobs) run if the app is in the stopped state.
+ final boolean canRun = !isStopped && !shouldStopImmediately
&& !mAppStateTracker.areJobsRestricted(
uid, packageName, jobStatus.canRunInBatterySaver());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b7480649..d1f575e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1102,6 +1102,12 @@
return job.getService();
}
+ /** Return the package name of the app that scheduled the job. */
+ public String getCallingPackageName() {
+ return job.getService().getPackageName();
+ }
+
+ /** Return the package name of the app on whose behalf the job was scheduled. */
public String getSourcePackageName() {
return sourcePackageName;
}
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..98a78cf 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1162,6 +1162,13 @@
field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
}
+ public static final class UserProperties.Builder {
+ ctor public UserProperties.Builder();
+ method @NonNull public android.content.pm.UserProperties build();
+ method @NonNull public android.content.pm.UserProperties.Builder setShowInQuietMode(int);
+ method @NonNull public android.content.pm.UserProperties.Builder setShowInSharingSurfaces(int);
+ }
+
}
package android.content.res {
@@ -1597,6 +1604,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/content/Intent.java b/core/java/android/content/Intent.java
index 38bcfa2..23a5d4d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2805,7 +2805,7 @@
* and the package in the stopped state cannot self-start for any reason unless there's an
* explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED}
* broadcast is sent when such an explicit process start occurs and the package is taken
- * out of the stopped state.
+ * out of the stopped state. The data contains the name of the package.
* </p>
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
@@ -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/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 445ca0c..56e8291 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -1076,6 +1076,8 @@
* Intended for building default values (and so all properties are present in the built object).
* @hide
*/
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
public static final class Builder {
// UserProperties fields and their default values.
private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
@@ -1099,54 +1101,82 @@
private boolean mDeleteAppWithParent = false;
private boolean mAlwaysVisible = false;
+ /**
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @TestApi
+ public Builder() {}
+
+ /** @hide */
public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
mShowInLauncher = showInLauncher;
return this;
}
+ /** @hide */
public Builder setStartWithParent(boolean startWithParent) {
mStartWithParent = startWithParent;
return this;
}
- /** Sets the value for {@link #mShowInSettings} */
+ /** Sets the value for {@link #mShowInSettings}
+ * @hide
+ */
public Builder setShowInSettings(@ShowInSettings int showInSettings) {
mShowInSettings = showInSettings;
return this;
}
- /** Sets the value for {@link #mShowInQuietMode} */
+ /** Sets the value for {@link #mShowInQuietMode}
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public Builder setShowInQuietMode(@ShowInQuietMode int showInQuietMode) {
mShowInQuietMode = showInQuietMode;
return this;
}
- /** Sets the value for {@link #mShowInSharingSurfaces}. */
+ /** Sets the value for {@link #mShowInSharingSurfaces}.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public Builder setShowInSharingSurfaces(@ShowInSharingSurfaces int showInSharingSurfaces) {
mShowInSharingSurfaces = showInSharingSurfaces;
return this;
}
- /** Sets the value for {@link #mInheritDevicePolicy}*/
+ /** Sets the value for {@link #mInheritDevicePolicy}
+ * @hide
+ */
public Builder setInheritDevicePolicy(
@InheritDevicePolicy int inheritRestrictionsDevicePolicy) {
mInheritDevicePolicy = inheritRestrictionsDevicePolicy;
return this;
}
+ /** @hide */
public Builder setUseParentsContacts(boolean useParentsContacts) {
mUseParentsContacts = useParentsContacts;
return this;
}
- /** Sets the value for {@link #mUpdateCrossProfileIntentFiltersOnOTA} */
+ /** Sets the value for {@link #mUpdateCrossProfileIntentFiltersOnOTA}
+ * @hide
+ */
public Builder setUpdateCrossProfileIntentFiltersOnOTA(boolean
updateCrossProfileIntentFiltersOnOTA) {
mUpdateCrossProfileIntentFiltersOnOTA = updateCrossProfileIntentFiltersOnOTA;
return this;
}
- /** Sets the value for {@link #mCrossProfileIntentFilterAccessControl} */
+ /** Sets the value for {@link #mCrossProfileIntentFilterAccessControl}
+ * @hide
+ */
public Builder setCrossProfileIntentFilterAccessControl(
@CrossProfileIntentFilterAccessControlLevel int
crossProfileIntentFilterAccessControl) {
@@ -1154,24 +1184,30 @@
return this;
}
- /** Sets the value for {@link #mCrossProfileIntentResolutionStrategy} */
+ /** Sets the value for {@link #mCrossProfileIntentResolutionStrategy}
+ * @hide
+ */
public Builder setCrossProfileIntentResolutionStrategy(@CrossProfileIntentResolutionStrategy
int crossProfileIntentResolutionStrategy) {
mCrossProfileIntentResolutionStrategy = crossProfileIntentResolutionStrategy;
return this;
}
+ /** @hide */
public Builder setMediaSharedWithParent(boolean mediaSharedWithParent) {
mMediaSharedWithParent = mediaSharedWithParent;
return this;
}
+ /** @hide */
public Builder setCredentialShareableWithParent(boolean credentialShareableWithParent) {
mCredentialShareableWithParent = credentialShareableWithParent;
return this;
}
- /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */
+ /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode}
+ * @hide
+ */
public Builder setAuthAlwaysRequiredToDisableQuietMode(
boolean authAlwaysRequiredToDisableQuietMode) {
mAuthAlwaysRequiredToDisableQuietMode =
@@ -1179,19 +1215,28 @@
return this;
}
- /** Sets the value for {@link #mDeleteAppWithParent}*/
+ /** Sets the value for {@link #mDeleteAppWithParent}
+ * @hide
+ */
public Builder setDeleteAppWithParent(boolean deleteAppWithParent) {
mDeleteAppWithParent = deleteAppWithParent;
return this;
}
- /** Sets the value for {@link #mAlwaysVisible}*/
+ /** Sets the value for {@link #mAlwaysVisible}
+ * @hide
+ */
public Builder setAlwaysVisible(boolean alwaysVisible) {
mAlwaysVisible = alwaysVisible;
return this;
}
- /** Builds a UserProperties object with *all* values populated. */
+ /** Builds a UserProperties object with *all* values populated.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ @NonNull
public UserProperties build() {
return new UserProperties(
mShowInLauncher,
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/security/FileIntegrityManager.java b/core/java/android/security/FileIntegrityManager.java
index dae3202..025aac9 100644
--- a/core/java/android/security/FileIntegrityManager.java
+++ b/core/java/android/security/FileIntegrityManager.java
@@ -53,10 +53,10 @@
* verification, although the app APIs are only made available to apps in a later SDK version.
* Only when this method returns true, the other fs-verity APIs in the same class can succeed.
*
- * <p>The app may not need this method and just call the other APIs (i.e. {@link
- * #setupFsVerity(File)} and {@link #getFsVerityDigest(File)}) normally and handle any failure.
- * If some app feature really depends on fs-verity (e.g. protecting integrity of a large file
- * download), an early check of support status may avoid any cost if it is to fail late.
+ * <p>The app may not need this method and just call the other APIs normally and handle any
+ * failure. If some app feature really depends on fs-verity (e.g. protecting integrity of a
+ * large file download), an early check of support status may avoid any cost if it is to fail
+ * late.
*
* <p>Note: for historical reasons this is named {@code isApkVeritySupported()} instead of
* {@code isFsVeritySupported()}. It has also been available since API level 30, predating the
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/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 14ec14b..966161f 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -31,6 +31,7 @@
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
+import static android.view.contentcapture.flags.Flags.runOnBackgroundThreadEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -209,14 +210,14 @@
binder = resultData.getBinder(EXTRA_BINDER);
if (binder == null) {
Log.wtf(TAG, "No " + EXTRA_BINDER + " extra result");
- mainSession.mHandler.post(() -> mainSession.resetSession(
+ mainSession.runOnContentCaptureThread(() -> mainSession.resetSession(
STATE_DISABLED | STATE_INTERNAL_ERROR));
return;
}
} else {
binder = null;
}
- mainSession.mHandler.post(() ->
+ mainSession.runOnContentCaptureThread(() ->
mainSession.onSessionStarted(resultCode, binder));
}
}
@@ -256,7 +257,13 @@
*/
void start(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@NonNull ComponentName component, int flags) {
- runOnContentCaptureThread(() -> startImpl(token, shareableActivityToken, component, flags));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(
+ () -> startImpl(token, shareableActivityToken, component, flags));
+ } else {
+ // Preserve the control arm behaviour.
+ startImpl(token, shareableActivityToken, component, flags);
+ }
}
private void startImpl(@NonNull IBinder token, @NonNull IBinder shareableActivityToken,
@@ -613,7 +620,12 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
public void flush(@FlushReason int reason) {
- runOnContentCaptureThread(() -> flushImpl(reason));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(() -> flushImpl(reason));
+ } else {
+ // Preserve the control arm behaviour.
+ flushImpl(reason);
+ }
}
private void flushImpl(@FlushReason int reason) {
@@ -904,7 +916,12 @@
/** public because is also used by ViewRootImpl */
public void notifyContentCaptureEvents(
@NonNull SparseArray<ArrayList<Object>> contentCaptureEvents) {
- runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+ if (runOnBackgroundThreadEnabled()) {
+ runOnContentCaptureThread(() -> notifyContentCaptureEventsImpl(contentCaptureEvents));
+ } else {
+ // Preserve the control arm behaviour.
+ notifyContentCaptureEventsImpl(contentCaptureEvents);
+ }
}
private void notifyContentCaptureEventsImpl(
@@ -1076,19 +1093,30 @@
* </p>
*/
private void runOnContentCaptureThread(@NonNull Runnable r) {
- if (!mHandler.getLooper().isCurrentThread()) {
- mHandler.post(r);
+ if (runOnBackgroundThreadEnabled()) {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ mHandler.post(r);
+ } else {
+ r.run();
+ }
} else {
- r.run();
+ // Preserve the control arm behaviour to always post to the handler.
+ mHandler.post(r);
}
}
private void clearAndRunOnContentCaptureThread(@NonNull Runnable r, int what) {
- if (!mHandler.getLooper().isCurrentThread()) {
+ if (runOnBackgroundThreadEnabled()) {
+ if (!mHandler.getLooper().isCurrentThread()) {
+ mHandler.removeMessages(what);
+ mHandler.post(r);
+ } else {
+ r.run();
+ }
+ } else {
+ // Preserve the control arm behaviour to always post to the handler.
mHandler.removeMessages(what);
mHandler.post(r);
- } else {
- r.run();
}
}
}
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/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/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/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index 78a6479..64b2a93 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -1,6 +1,13 @@
package: "com.android.graphics.hwui.flags"
flag {
+ name: "matrix_44"
+ namespace: "core_graphics"
+ description: "API for 4x4 matrix and related canvas functions"
+ bug: "280116960"
+}
+
+flag {
name: "limited_hdr"
namespace: "core_graphics"
description: "API to enable apps to restrict the amount of HDR headroom that is used"
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/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 658b45f..2986504 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -17,11 +17,13 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
/**
* Defines the behavior of the [SceneTransitionLayout] when a scrollable component is scrolled.
@@ -32,8 +34,9 @@
*/
enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) {
/**
- * During scene transitions, scroll events are consumed by the [SceneTransitionLayout] instead
- * of the scrollable component.
+ * During scene transitions, if we are within
+ * [SceneTransitionLayoutImpl.transitionInterceptionThreshold], the [SceneTransitionLayout]
+ * consumes scroll events instead of the scrollable component.
*/
DuringTransitionBetweenScenes(canStartOnPostFling = false),
@@ -72,21 +75,101 @@
orientation: Orientation,
startBehavior: NestedScrollBehavior,
endBehavior: NestedScrollBehavior,
-): Modifier = composed {
- val connection =
- remember(layoutImpl, orientation, startBehavior, endBehavior) {
+) =
+ this then
+ NestedScrollToSceneElement(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+private data class NestedScrollToSceneElement(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val orientation: Orientation,
+ private val startBehavior: NestedScrollBehavior,
+ private val endBehavior: NestedScrollBehavior,
+) : ModifierNodeElement<NestedScrollToSceneNode>() {
+ override fun create() =
+ NestedScrollToSceneNode(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+ override fun update(node: NestedScrollToSceneNode) {
+ node.update(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+ }
+
+ override fun InspectorInfo.inspectableProperties() {
+ name = "nestedScrollToScene"
+ properties["layoutImpl"] = layoutImpl
+ properties["orientation"] = orientation
+ properties["startBehavior"] = startBehavior
+ properties["endBehavior"] = endBehavior
+ }
+}
+
+private class NestedScrollToSceneNode(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+ startBehavior: NestedScrollBehavior,
+ endBehavior: NestedScrollBehavior,
+) : DelegatingNode() {
+ private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
+ scenePriorityNestedScrollConnection(
+ layoutImpl = layoutImpl,
+ orientation = orientation,
+ startBehavior = startBehavior,
+ endBehavior = endBehavior,
+ )
+
+ private var nestedScrollNode: DelegatableNode =
+ nestedScrollModifierNode(
+ connection = priorityNestedScrollConnection,
+ dispatcher = null,
+ )
+
+ override fun onAttach() {
+ delegate(nestedScrollNode)
+ }
+
+ override fun onDetach() {
+ // Make sure we reset the scroll connection when this modifier is removed from composition
+ priorityNestedScrollConnection.reset()
+ }
+
+ fun update(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+ startBehavior: NestedScrollBehavior,
+ endBehavior: NestedScrollBehavior,
+ ) {
+ // Clean up the old nested scroll connection
+ priorityNestedScrollConnection.reset()
+ undelegate(nestedScrollNode)
+
+ // Create a new nested scroll connection
+ priorityNestedScrollConnection =
scenePriorityNestedScrollConnection(
layoutImpl = layoutImpl,
orientation = orientation,
startBehavior = startBehavior,
- endBehavior = endBehavior
+ endBehavior = endBehavior,
)
- }
-
- // Make sure we reset the scroll connection when this modifier is removed from composition
- DisposableEffect(connection) { onDispose { connection.reset() } }
-
- nestedScroll(connection = connection)
+ nestedScrollNode =
+ nestedScrollModifierNode(
+ connection = priorityNestedScrollConnection,
+ dispatcher = null,
+ )
+ delegate(nestedScrollNode)
+ }
}
private fun scenePriorityNestedScrollConnection(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ea3006f..8896e6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -83,7 +84,7 @@
underTest =
CommunalEditModeViewModel(
withDeps.communalInteractor,
- shadeViewController,
+ Provider { shadeViewController },
powerManager,
mediaHost,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 9bd0835..7fbcae0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -84,7 +85,7 @@
CommunalViewModel(
withDeps.communalInteractor,
withDeps.tutorialInteractor,
- shadeViewController,
+ Provider { shadeViewController },
powerManager,
mediaHost,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c110de9..70be031 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -63,6 +63,7 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.Ignore
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -395,6 +396,7 @@
}
@Test
+ @Ignore("b/315130482")
fun deviceGoesToSleep_wakeUp_unlock() =
testScope.runTest {
unlockDevice()
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/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index bed4283..333fc19 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -24,13 +24,14 @@
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.shade.ShadeViewController
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** The base view model for the communal hub. */
abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
- private val shadeViewController: ShadeViewController,
+ private val shadeViewController: Provider<ShadeViewController>,
private val powerManager: PowerManager,
val mediaHost: MediaHost,
) {
@@ -48,7 +49,7 @@
fun onOuterTouch(motionEvent: MotionEvent) {
// Forward the touch to the shade so that basic gestures like swipe up/down for
// shade/bouncer work.
- shadeViewController.handleExternalTouch(motionEvent)
+ shadeViewController.get().handleExternalTouch(motionEvent)
}
// TODO(b/308813166): remove once CommunalContainer is moved lower in z-order and doesn't block
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index b6843c5..c82e000 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -25,6 +25,7 @@
import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
/** The view model for communal hub in edit mode. */
@@ -33,7 +34,7 @@
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
- shadeViewController: ShadeViewController,
+ shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index d7dcdb9..abf1986 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -26,6 +26,7 @@
import com.android.systemui.shade.ShadeViewController
import javax.inject.Inject
import javax.inject.Named
+import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -39,7 +40,7 @@
constructor(
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
- shadeViewController: ShadeViewController,
+ shadeViewController: Provider<ShadeViewController>,
powerManager: PowerManager,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
) : BaseCommunalViewModel(communalInteractor, shadeViewController, powerManager, mediaHost) {
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/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 017dac2..20da00e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -43,6 +43,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -81,6 +82,7 @@
private val interactionJankMonitor: InteractionJankMonitor,
private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
private val vibratorHelper: VibratorHelper,
+ private val falsingManager: FalsingManager,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -155,6 +157,7 @@
interactionJankMonitor,
deviceEntryHapticsInteractor,
vibratorHelper,
+ falsingManager,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2d6c0e1..b51edab6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -157,6 +157,9 @@
val lastDozeTapToWakePosition: StateFlow<Point?>
+ /** Last point that [KeyguardRootView] was tapped */
+ val lastRootViewTapPosition: MutableStateFlow<Point?>
+
/** Observable for the [StatusBarState] */
val statusBarState: StateFlow<StatusBarState>
@@ -418,6 +421,8 @@
_lastDozeTapToWakePosition.value = position
}
+ override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
+
override val isDreamingWithOverlay: Flow<Boolean> =
conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index b8c3925..702386d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -171,6 +171,9 @@
/** Whether the keyguard is going away. */
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
+ /** Last point that [KeyguardRootView] view was tapped */
+ val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
+
/** Whether the primary bouncer is showing or not. */
val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
@@ -304,6 +307,10 @@
repository.setClockShouldBeCentered(shouldBeCentered)
}
+ fun setLastRootViewTapPosition(point: Point?) {
+ repository.lastRootViewTapPosition.value = point
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 02c6f33..e603ead 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -19,6 +19,8 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.DrawableRes
+import android.annotation.SuppressLint
+import android.graphics.Point
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
@@ -47,6 +49,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -73,6 +76,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
object KeyguardRootViewBinder {
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: ViewGroup,
@@ -87,6 +91,7 @@
interactionJankMonitor: InteractionJankMonitor?,
deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
vibratorHelper: VibratorHelper?,
+ falsingManager: FalsingManager?,
): DisposableHandle {
var onLayoutChangeListener: OnLayoutChange? = null
val childViews = mutableMapOf<Int, View>()
@@ -94,6 +99,16 @@
val burnInLayerId = R.id.burn_in_layer
val aodNotificationIconContainerId = R.id.aod_notification_icon_container
val largeClockId = R.id.lockscreen_clock_view_large
+
+ if (keyguardBottomAreaRefactor()) {
+ view.setOnTouchListener { _, event ->
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+ }
+ false
+ }
+ }
+
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 8514225..11e63e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.binder
+import android.graphics.Rect
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
@@ -25,6 +26,8 @@
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
@@ -35,12 +38,15 @@
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
object KeyguardSettingsViewBinder {
fun bind(
parentView: View,
viewModel: KeyguardSettingsMenuViewModel,
+ longPressViewModel: KeyguardLongPressViewModel,
+ rootViewModel: KeyguardRootViewModel,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
): DisposableHandle {
@@ -88,6 +94,18 @@
}
}
}
+
+ launch {
+ rootViewModel.lastRootViewTapPosition.filterNotNull().collect { point ->
+ if (view.isVisible) {
+ val hitRect = Rect()
+ view.getHitRect(hitRect)
+ if (!hitRect.contains(point.x, point.y)) {
+ longPressViewModel.onTouchedOutside()
+ }
+ }
+ }
+ }
}
}
return disposableHandle
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 4eecfde..03e45fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -376,6 +376,7 @@
null, // jank monitor not required for preview mode
null, // device entry haptics not required preview mode
null, // device entry haptics not required for preview mode
+ null, // falsing manager not required for preview mode
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 9a33f08..4bc2d86 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -29,15 +29,15 @@
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import androidx.core.view.isVisible
import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.res.R
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -47,6 +47,8 @@
constructor(
@Main private val resources: Resources,
private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
+ private val keyguardLongPressViewModel: KeyguardLongPressViewModel,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
private val vibratorHelper: VibratorHelper,
private val activityStarter: ActivityStarter,
) : KeyguardSection() {
@@ -73,6 +75,8 @@
KeyguardSettingsViewBinder.bind(
constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
keyguardSettingsMenuViewModel,
+ keyguardLongPressViewModel,
+ keyguardRootViewModel,
vibratorHelper,
activityStarter,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 97ddbb0..4588e02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.graphics.Point
import android.util.MathUtils
import android.view.View.VISIBLE
import com.android.app.animation.Interpolators
@@ -101,6 +102,9 @@
val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD)
+ /** Last point that the root view was tapped */
+ val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
+
/** the shared notification container bounds *on the lockscreen* */
val notificationBounds: StateFlow<NotificationContainerBounds> =
keyguardInteractor.notificationContainerBounds
@@ -262,4 +266,8 @@
}
.toAnimatedValueFlow()
}
+
+ fun setRootViewLastTapPosition(point: Point) {
+ keyguardInteractor.setLastRootViewTapPosition(point)
+ }
}
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/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/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 93bc960..af6da3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -146,7 +146,7 @@
* When you just need a dialog, call this.
*/
public SystemUIDialog create() {
- return create(new DialogDelegate<>(){});
+ return create(new DialogDelegate<>(){}, mContext);
}
/**
@@ -155,13 +155,18 @@
*
* When you need to customize the dialog, pass it a delegate.
*/
- public SystemUIDialog create(Delegate delegate) {
- return create((DialogDelegate<SystemUIDialog>) delegate);
+ public SystemUIDialog create(Delegate delegate, Context context) {
+ return create((DialogDelegate<SystemUIDialog>) delegate, context);
}
- private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate) {
+ public SystemUIDialog create(Delegate delegate) {
+ return create(delegate, mContext);
+ }
+
+ private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate,
+ Context context) {
return new SystemUIDialog(
- mContext,
+ context,
DEFAULT_THEME,
DEFAULT_DISMISS_ON_DEVICE_LOCK,
mFeatureFlags,
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/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 81a7bec..0e7c662 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -119,6 +119,8 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha: StateFlow<Float> = _keyguardAlpha
+ override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
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/Android.bp b/services/core/Android.bp
index 8ed3fd6..b4cf34e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -201,6 +201,7 @@
"biometrics_flags_lib",
"am_flags_lib",
"com_android_wm_shell_flags_lib",
+ "com.android.server.utils_aconfig-java",
"service-jobscheduler-deviceidle.flags-aconfig-java",
],
javac_shard_size: 50,
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index eb3ec24..05d07ae 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1464,15 +1464,17 @@
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
if (android.security.Flags.binaryTransparencySepolicyHash()) {
- byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
- "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
- String sepolicyHashEncoded = null;
- if (sepolicyHash != null) {
- sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
- Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
- }
- FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
- sepolicyHashEncoded, mVbmetaDigest);
+ IoThread.getExecutor().execute(() -> {
+ byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
+ "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
+ String sepolicyHashEncoded = null;
+ if (sepolicyHash != null) {
+ sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
+ Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
+ sepolicyHashEncoded, mVbmetaDigest);
+ });
}
}
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/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5f1a7e7..7191684 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -241,6 +241,7 @@
import com.android.server.am.ServiceRecord.ShortFgsInfo;
import com.android.server.pm.KnownPackages;
import com.android.server.uri.NeededUriGrants;
+import com.android.server.utils.AnrTimer;
import com.android.server.wm.ActivityServiceConnectionsHolder;
import java.io.FileDescriptor;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 469f209..2d687de 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -475,6 +475,7 @@
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.utils.AnrTimer;
import com.android.server.utils.PriorityDump;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index ad49991..2cac7a0 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -88,6 +88,7 @@
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.utils.AnrTimer;
import dalvik.annotation.optimization.NeverCompile;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 2ed079a..d9e8ddd 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -9,14 +9,6 @@
}
flag {
- name: "anr_timer_service_enabled"
- namespace: "system_performance"
- is_fixed_read_only: true
- description: "Feature flag for the ANR timer service"
- bug: "282428924"
-}
-
-flag {
name: "fgs_abuse_detection"
namespace: "backstage_power"
description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)."
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index cd3f0f0..1da7f0c 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
-import android.content.pm.PackageManagerInternal;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
@@ -29,7 +28,6 @@
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
public class SyncJobService extends JobService {
private static final String TAG = "SyncManager";
@@ -99,20 +97,6 @@
return true;
}
- // TODO(b/209852664): remove this logic from here once it's added within JobScheduler.
- // JobScheduler should not call onStartJob for syncs whose source packages are stopped.
- // Until JS adds the relevant logic, this is a temporary solution to keep deferring syncs
- // for packages in the stopped state.
- if (android.content.pm.Flags.stayStopped()) {
- if (LocalServices.getService(PackageManagerInternal.class)
- .isPackageStopped(op.owningPackage, op.target.userId)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Slog.d(TAG, "Skipping sync for force-stopped package: " + op.owningPackage);
- }
- return false;
- }
- }
-
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
synchronized (sLock) {
final int jobId = params.getJobId();
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/utils/Android.bp b/services/core/java/com/android/server/utils/Android.bp
new file mode 100644
index 0000000..3a334be
--- /dev/null
+++ b/services/core/java/com/android/server/utils/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "com.android.server.utils-aconfig",
+ package: "com.android.server.utils",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "com.android.server.utils_aconfig-java",
+ aconfig_declarations: "com.android.server.utils-aconfig",
+}
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
similarity index 98%
rename from services/core/java/com/android/server/am/AnrTimer.java
rename to services/core/java/com/android/server/utils/AnrTimer.java
index 3e17930..2b6dffb 100644
--- a/services/core/java/com/android/server/am/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.am;
+package com.android.server.utils;
import static android.text.TextUtils.formatSimple;
@@ -77,7 +77,7 @@
*
* @hide
*/
-class AnrTimer<V> {
+public class AnrTimer<V> {
/**
* The log tag.
@@ -568,7 +568,7 @@
* @param label A name for this instance.
* @param extend A flag to indicate if expired timers can be granted extensions.
*/
- AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
+ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
this(handler, what, label, extend, new Injector(handler));
}
@@ -580,7 +580,7 @@
* @param what The "what" parameter for the expiration message.
* @param label A name for this instance.
*/
- AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
+ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
this(handler, what, label, false);
}
@@ -591,7 +591,7 @@
*
* @return true if the service is flag-enabled.
*/
- boolean serviceEnabled() {
+ public boolean serviceEnabled() {
return mFeature.enabled();
}
@@ -856,7 +856,7 @@
* @param timeoutMs The timer timeout, in milliseconds.
* @return true if the timer was successfully created.
*/
- boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
return mFeature.start(arg, pid, uid, timeoutMs);
}
@@ -867,7 +867,7 @@
*
* @return true if the timer was found and was running.
*/
- boolean cancel(@NonNull V arg) {
+ public boolean cancel(@NonNull V arg) {
return mFeature.cancel(arg);
}
@@ -878,7 +878,7 @@
*
* @return true if the timer was found and was expired.
*/
- boolean accept(@NonNull V arg) {
+ public boolean accept(@NonNull V arg) {
return mFeature.accept(arg);
}
@@ -892,7 +892,7 @@
*
* @return true if the timer was found and was expired.
*/
- boolean discard(@NonNull V arg) {
+ public boolean discard(@NonNull V arg) {
return mFeature.discard(arg);
}
@@ -1010,7 +1010,7 @@
/**
* Dumpsys output.
*/
- static void dump(@NonNull PrintWriter pw, boolean verbose) {
+ public static void dump(@NonNull PrintWriter pw, boolean verbose) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
ipw.println("AnrTimer statistics");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
new file mode 100644
index 0000000..489e21a
--- /dev/null
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.utils"
+
+flag {
+ name: "anr_timer_service_enabled"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Feature flag for the ANR timer service"
+ bug: "282428924"
+}
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/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4625b4fe..f8b22c9 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -614,6 +614,15 @@
== ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
if (callerCanAllow && realCallerCanAllow) {
// Both caller and real caller allow with system defined behavior
+ if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
+ // Will be allowed even with BAL hardening.
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed by caller. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ // return the realCaller result for backwards compatibility
+ return statsLog(resultForRealCaller, state);
+ }
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG,
"With Android 15 BAL hardening this activity start may be blocked"
@@ -632,6 +641,14 @@
}
if (callerCanAllow) {
// Allowed before V by creator
+ if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
+ // Will be allowed even with BAL hardening.
+ if (DEBUG_ACTIVITY_STARTS) {
+ Slog.d(TAG, "Activity start allowed by caller. "
+ + state.dump(resultForCaller, resultForRealCaller));
+ }
+ return statsLog(resultForCaller, state);
+ }
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
Slog.wtf(TAG,
"With Android 15 BAL hardening this activity start may be blocked"
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/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
new file mode 100644
index 0000000..cdae8c6
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.job.controllers;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppStateTracker;
+import com.android.server.AppStateTrackerImpl;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundJobsControllerTest {
+ private static final int CALLING_UID = 1000;
+ private static final String CALLING_PACKAGE = "com.test.calling.package";
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_UID = 10001;
+ private static final int ALTERNATE_UID = 12345;
+ private static final String ALTERNATE_SOURCE_PACKAGE = "com.test.alternate.package";
+ private static final int SOURCE_USER_ID = 0;
+
+ private BackgroundJobsController mBackgroundJobsController;
+ private BroadcastReceiver mStoppedReceiver;
+ private JobStore mJobStore;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private AppStateTrackerImpl mAppStateTrackerImpl;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .mockStatic(LocalServices.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ // Called in BackgroundJobsController constructor.
+ doReturn(mock(ActivityManagerInternal.class))
+ .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mAppStateTrackerImpl)
+ .when(() -> LocalServices.getService(AppStateTracker.class));
+ doReturn(mPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
+ when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+ // Called in JobStatus constructor.
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+
+ doReturn(false).when(mAppStateTrackerImpl)
+ .areJobsRestricted(anyInt(), anyString(), anyBoolean());
+ doReturn(true).when(mAppStateTrackerImpl)
+ .isRunAnyInBackgroundAppOpsAllowed(anyInt(), anyString());
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ mBackgroundJobsController = new BackgroundJobsController(mJobSchedulerService);
+ mBackgroundJobsController.startTrackingLocked();
+
+ verify(mContext).registerReceiverAsUser(receiverCaptor.capture(), any(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(Intent.ACTION_PACKAGE_RESTARTED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_UNSTOPPED)),
+ any(), any());
+ mStoppedReceiver = receiverCaptor.getValue();
+
+ // Need to do this since we're using a mock JS and not a real object.
+ doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(SOURCE_UID);
+ doReturn(new ArraySet<>(new String[]{ALTERNATE_SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(ALTERNATE_UID);
+ setPackageUid(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE);
+ setPackageUid(SOURCE_UID, SOURCE_PACKAGE);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setPackageUid(final int uid, final String pkgName) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
+ private void setStoppedState(int uid, String pkgName, boolean stopped) {
+ Intent intent = new Intent(
+ stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, pkgName, null));
+ mStoppedReceiver.onReceive(mContext, intent);
+ }
+
+ private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
+ }
+ }
+
+ private void trackJobs(JobStatus... jobs) {
+ for (JobStatus job : jobs) {
+ mJobStore.add(job);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.maybeStartTrackingJobLocked(job, null);
+ }
+ }
+ }
+
+ private JobInfo.Builder createBaseJobInfoBuilder(String pkgName, int jobId) {
+ final ComponentName cn = spy(new ComponentName(pkgName, "TestBJCJobService"));
+ doReturn("TestBJCJobService").when(cn).flattenToShortString();
+ return new JobInfo.Builder(jobId, cn);
+ }
+
+ private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
+ JobInfo jobInfo) {
+ JobStatus js = JobStatus.createFromJobInfo(
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "BJCTest", testTag);
+ js.serviceProcessName = "testProcess";
+ // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+ js.setStandbyBucket(FREQUENT_INDEX);
+ return js;
+ }
+
+ @Test
+ public void testStopped_disabled() {
+ mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+
+ @Test
+ public void testStopped_enabled() {
+ mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertFalse(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertFalse(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
deleted file mode 100644
index 44d6760..0000000
--- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
+++ /dev/null
@@ -1,389 +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.am;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.Presubmit;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Build/Install/Run:
- * atest FrameworksServicesTests:AnrTimerTest
- */
-@SmallTest
-@Presubmit
-public class AnrTimerTest {
-
- /**
- * A handler that allows control over when to dispatch messages and callbacks. Because most
- * Handler methods are final, the only thing this handler can intercept is sending messages.
- * This handler allows unit tests to be written without a need to sleep (which leads to flaky
- * tests).
- *
- * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}.
- */
- static class TestHandler extends Handler {
-
- private boolean mImmediate = true;
- private ArrayList<Message> mQueuedMessages = new ArrayList<>();
-
- ArrayList<Long> mDelays = new ArrayList<>();
-
- TestHandler(Looper looper, Callback callback, boolean immediate) {
- super(looper, callback);
- mImmediate = immediate;
- }
-
- TestHandler(Looper looper, Callback callback) {
- this(looper, callback, true);
- }
-
- /**
- * Override sendMessageAtTime. In immediate mode, the message is immediately dispatched.
- * In non-immediate mode, the message is enqueued to the real handler. In both cases, the
- * original delay is computed by comparing the target dispatch time with 'now'. This
- * computation is prone to errors if the code experiences delays. The computed time is
- * captured in the mDelays list.
- */
- @Override
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- long delay = uptimeMillis - SystemClock.uptimeMillis();
- mDelays.add(delay);
- if (mImmediate) {
- mQueuedMessages.add(msg);
- dispatchQueuedMessages();
- } else {
- super.sendMessageAtTime(msg, uptimeMillis);
- }
- return true;
- }
-
- void setImmediate(boolean immediate) {
- mImmediate = immediate;
- }
-
- /** Dispatch any messages that have been queued on the calling thread. */
- void dispatchQueuedMessages() {
- ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
- mQueuedMessages.clear();
- for (Message msg : messages) {
- dispatchMessage(msg);
- }
- }
-
- /**
- * Compare the captured delays with the input array. The comparison is fuzzy because the
- * captured delay (see sendMessageAtTime) is affected by process delays.
- */
- void verifyDelays(long[] r) {
- final long FUZZ = 10;
- assertEquals(r.length, mDelays.size());
- for (int i = 0; i < mDelays.size(); i++) {
- long t = r[i];
- long v = mDelays.get(i);
- assertTrue(v >= t - FUZZ && v <= t + FUZZ);
- }
- }
- }
-
- private Handler mHandler;
- private CountDownLatch mLatch = null;
- private ArrayList<Message> mMessages;
-
- // The commonly used message timeout key.
- private static final int MSG_TIMEOUT = 1;
-
- @Before
- public void setUp() {
- mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
- mMessages = new ArrayList<>();
- mLatch = new CountDownLatch(1);
- AnrTimer.resetTimerListForHermeticTest();
- }
-
- @After
- public void tearDown() {
- mHandler = null;
- mMessages = null;
- }
-
- // When a timer expires, set the expiration time in the message and add it to the queue.
- private boolean expirationHandler(Message msg) {
- mMessages.add(Message.obtain(msg));
- mLatch.countDown();
- return false;
- }
-
- // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
- // different message instances.
- private static class TestArg {
- final int pid;
- final int uid;
- final int tag;
-
- TestArg(int pid, int uid, int tag) {
- this.pid = pid;
- this.uid = uid;
- this.tag = tag;
- }
- @Override
- public String toString() {
- return String.format("pid=%d uid=%d tag=%d", pid, uid, tag);
- }
- }
-
- /**
- * An instrumented AnrTimer.
- */
- private class TestAnrTimer extends AnrTimer {
- // A local copy of 'what'. The field in AnrTimer is private.
- final int mWhat;
-
- TestAnrTimer(Handler h, int key, String tag) {
- super(h, key, tag);
- mWhat = key;
- }
-
- TestAnrTimer() {
- this(mHandler, MSG_TIMEOUT, caller());
- }
-
- TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) {
- super(h, key, tag, extend, injector);
- mWhat = key;
- }
-
- TestAnrTimer(boolean extend, TestInjector injector) {
- this(mHandler, MSG_TIMEOUT, caller(), extend, injector);
- }
-
- // Return the name of method that called the constructor, assuming that this function is
- // called from inside the constructor. The calling method is used to name the AnrTimer
- // instance so that logs are easier to understand.
- private static String caller() {
- final int n = 4;
- StackTraceElement[] stack = Thread.currentThread().getStackTrace();
- if (stack.length < n+1) return "test";
- return stack[n].getMethodName();
- }
-
- boolean start(TestArg arg, long millis) {
- return start(arg, arg.pid, arg.uid, millis);
- }
-
- int what() {
- return mWhat;
- }
- }
-
- private static class TestTracker extends AnrTimer.CpuTracker {
- long index = 0;
- final int skip;
- TestTracker(int skip) {
- this.skip = skip;
- }
- long delay(int pid) {
- return index++ * skip;
- }
- }
-
- private class TestInjector extends AnrTimer.Injector {
- final boolean mImmediate;
- final AnrTimer.CpuTracker mTracker;
- TestHandler mTestHandler;
-
- TestInjector(int skip, boolean immediate) {
- super(mHandler);
- mTracker = new TestTracker(skip);
- mImmediate = immediate;
- }
-
- TestInjector(int skip) {
- this(skip, true);
- }
-
- @Override
- Handler newHandler(Handler.Callback callback) {
- if (mTestHandler == null) {
- mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
- }
- return mTestHandler;
- }
-
- /** Fetch the allocated handle. This does not check for nulls. */
- TestHandler getHandler() {
- return mTestHandler;
- }
-
- /**
- * This override returns the tracker supplied in the constructor. It does not create a
- * new one.
- */
- @Override
- AnrTimer.CpuTracker newTracker() {
- return mTracker;
- }
-
- /** For test purposes, always enable the feature. */
- @Override
- boolean isFeatureEnabled() {
- return true;
- }
- }
-
- // Tests
- // 1. Start a timer and wait for expiration.
- // 2. Start a timer and cancel it. Verify no expiration.
- // 3. Start a timer. Shortly thereafter, restart it. Verify only one expiration.
- // 4. Start a couple of timers. Verify max active timers. Discard one and verify the active
- // count drops by 1. Accept one and verify the active count drops by 1.
-
- @Test
- public void testSimpleTimeout() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(0);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
- // Delivery is immediate but occurs on a different thread.
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the timer is still present.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
-
- // Verify that the timer no longer exists.
- assertFalse(timer.accept(t));
- }
-
- @Test
- public void testCancel() throws Exception {
- // Create an non-immediate TestHandler.
- TestInjector injector = new TestInjector(0, false);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
-
- Handler handler = injector.getHandler();
- assertNotNull(handler);
- assertTrue(handler instanceof TestHandler);
-
- // The tests that follow check for a 'what' of 0 (zero), which is the message key used
- // by AnrTimer internally.
- TestArg t = new TestArg(1, 1, 3);
- assertFalse(handler.hasMessages(0));
- assertTrue(timer.start(t, 100));
- assertTrue(handler.hasMessages(0));
- assertTrue(timer.cancel(t));
- assertFalse(handler.hasMessages(0));
-
- // Verify that no expiration messages were delivered.
- assertEquals(0, mMessages.size());
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testRestart() throws Exception {
- // Create an non-immediate TestHandler.
- TestInjector injector = new TestInjector(0, false);
- TestAnrTimer timer = new TestAnrTimer(false, injector);
-
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 2500));
- assertTrue(timer.start(t, 1000));
-
- // Verify that the test handler saw two timeouts.
- injector.getHandler().verifyDelays(new long[] { 2500, 1000 });
-
- // Verify that there is a single timer. Then cancel it.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.cancel(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testExtendNormal() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(5);
- TestAnrTimer timer = new TestAnrTimer(true, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
-
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms.
- injector.getHandler().verifyDelays(new long[] { 10, 5 });
-
- // Verify that the timer is still present. Then remove it and verify that the list is
- // empty.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-
- @Test
- public void testExtendOversize() throws Exception {
- // Create an immediate TestHandler.
- TestInjector injector = new TestInjector(25);
- TestAnrTimer timer = new TestAnrTimer(true, injector);
- TestArg t = new TestArg(1, 1, 3);
- assertTrue(timer.start(t, 10));
-
- assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
- assertEquals(1, mMessages.size());
- Message m = mMessages.get(0);
- assertEquals(timer.what(), m.what);
- assertEquals(t, m.obj);
-
- // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms.
- injector.getHandler().verifyDelays(new long[] { 10, 10 });
-
- // Verify that the timer is still present. Then remove it and verify that the list is
- // empty.
- assertEquals(1, AnrTimer.sizeOfTimerList());
- assertTrue(timer.accept(t));
- assertEquals(0, AnrTimer.sizeOfTimerList());
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
new file mode 100644
index 0000000..330dbb8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@Presubmit
+public class AnrTimerTest {
+
+ // The commonly used message timeout key.
+ private static final int MSG_TIMEOUT = 1;
+
+ // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
+ // different message instances. Additional fields (like what) capture delivery information
+ // that is checked by the test.
+ private static class TestArg {
+ final int pid;
+ final int uid;
+ int what;
+
+ TestArg(int pid, int uid) {
+ this.pid = pid;
+ this.uid = uid;
+ this.what = 0;
+ }
+ }
+
+ /**
+ * The test handler is a self-contained object for a single test.
+ */
+ private static class Helper {
+ final Object mLock = new Object();
+
+ final Handler mHandler;
+ final CountDownLatch mLatch;
+ @GuardedBy("mLock")
+ final ArrayList<TestArg> mMessages;
+
+ Helper(int expect) {
+ mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
+ mMessages = new ArrayList<>();
+ mLatch = new CountDownLatch(expect);
+ }
+
+ /**
+ * When a timer expires, the object must be a TestArg. Update the TestArg with
+ * expiration metadata and save it.
+ */
+ private boolean expirationHandler(Message msg) {
+ synchronized (mLock) {
+ TestArg arg = (TestArg) msg.obj;
+ arg.what = msg.what;
+ mMessages.add(arg);
+ mLatch.countDown();
+ return false;
+ }
+ }
+
+ boolean await(long timeout) throws InterruptedException {
+ // No need to synchronize, as the CountDownLatch is already thread-safe.
+ return mLatch.await(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Fetch the received messages. Fail if the count of received messages is other than the
+ * expected count.
+ */
+ TestArg[] messages(int expected) {
+ synchronized (mLock) {
+ assertEquals(expected, mMessages.size());
+ return mMessages.toArray(new TestArg[expected]);
+ }
+ }
+ }
+
+ /**
+ * An instrumented AnrTimer.
+ */
+ private static class TestAnrTimer extends AnrTimer<TestArg> {
+ private TestAnrTimer(Handler h, int key, String tag) {
+ super(h, key, tag);
+ }
+
+ TestAnrTimer(Helper helper) {
+ this(helper.mHandler, MSG_TIMEOUT, caller());
+ }
+
+ void start(TestArg arg, long millis) {
+ start(arg, arg.pid, arg.uid, millis);
+ }
+
+ // Return the name of method that called the constructor, assuming that this function is
+ // called from inside the constructor. The calling method is used to name the AnrTimer
+ // instance so that logs are easier to understand.
+ private static String caller() {
+ final int n = 4;
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ if (stack.length < n+1) return "test";
+ return stack[n].getMethodName();
+ }
+ }
+
+ void validate(TestArg expected, TestArg actual) {
+ assertEquals(expected, actual);
+ assertEquals(actual.what, MSG_TIMEOUT);
+ }
+
+
+ /**
+ * Verify that a simple expiration succeeds. The timer is started for 10ms. The test
+ * procedure waits 5s for the expiration message, but under correct operation, the test will
+ * only take 10ms
+ */
+ @Test
+ public void testSimpleTimeout() throws Exception {
+ Helper helper = new Helper(1);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t = new TestArg(1, 1);
+ timer.start(t, 10);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(1);
+ validate(t, result[0]);
+ }
+
+ /**
+ * Verify that if three timers are scheduled, they are delivered in time order.
+ */
+ @Test
+ public void testMultipleTimers() throws Exception {
+ // Expect three messages.
+ Helper helper = new Helper(3);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t1 = new TestArg(1, 1);
+ TestArg t2 = new TestArg(1, 2);
+ TestArg t3 = new TestArg(1, 3);
+ timer.start(t1, 50);
+ timer.start(t2, 60);
+ timer.start(t3, 40);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(3);
+ validate(t3, result[0]);
+ validate(t1, result[1]);
+ validate(t2, result[2]);
+ }
+
+ /**
+ * Verify that a canceled timer is not delivered.
+ */
+ @Test
+ public void testCancelTimer() throws Exception {
+ // Expect two messages.
+ Helper helper = new Helper(2);
+ TestAnrTimer timer = new TestAnrTimer(helper);
+ TestArg t1 = new TestArg(1, 1);
+ TestArg t2 = new TestArg(1, 2);
+ TestArg t3 = new TestArg(1, 3);
+ timer.start(t1, 50);
+ timer.start(t2, 60);
+ timer.start(t3, 40);
+ // Briefly pause.
+ assertFalse(helper.await(10));
+ timer.cancel(t1);
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(helper.await(5000));
+ TestArg[] result = helper.messages(2);
+ validate(t3, result[0]);
+ validate(t2, result[1]);
+ }
+}
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/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index ad272a0..ce92eac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -40,10 +40,9 @@
constructor(
protected val flicker: LegacyFlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
- protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
- init {
- tapl.setExpectedRotationCheckEnabled(true)
+ protected val tapl: LauncherInstrumentation by lazy {
+ LauncherInstrumentation().also { it.expectedRotationCheckEnabled = true }
}
private val logTag = this::class.java.simpleName
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