Merge "Don't add the "Events" mode anymore, and delete it if disabled" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index dd919ca..a0f38d9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -680,6 +680,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "com.android.media.flags.editing-aconfig-cc",
+ aconfig_declarations: "com.android.media.flags.editing-aconfig",
+}
+
// MediaProjection
aconfig_declarations {
name: "com.android.media.flags.projection-aconfig",
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 384add5..2ab16e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2397,7 +2397,11 @@
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
} else {
- mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+ mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ } else {
+ Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control");
+ }
}
return ret;
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e1402f8..b6aad11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -266,3 +266,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "remove_starting_window_wait_for_multi_transitions"
+ namespace: "windowing_frontend"
+ description: "Avoid remove starting window too early when playing multiple transitions"
+ bug: "362347290"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbc058c..b0e38e2 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -122,18 +122,20 @@
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, () -> {}, groups);
}
- public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, cacheUpdater, groups);
}
public PerfettoProtoLogImpl(
@NonNull String viewerConfigFilePath,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath,
null,
new ProtoLogViewerConfigReader(() -> {
@@ -177,12 +179,14 @@
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
cacheUpdater, groups,
ProtoLogDataSource::new,
- IProtoLogConfigurationService.Stub
- .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE))
+ android.tracing.Flags.clientSideProtoLogging() ?
+ IProtoLogConfigurationService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE)
+ ) : null
);
}
@@ -222,7 +226,7 @@
if (android.tracing.Flags.clientSideProtoLogging()) {
mProtoLogConfigurationService = configurationService;
Objects.requireNonNull(mProtoLogConfigurationService,
- "ServiceManager returned a null ProtoLog Configuration Service");
+ "A null ProtoLog Configuration Service was provided!");
try {
var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index bf77db7..adf03fe 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import android.os.ServiceManager;
+
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
@@ -76,7 +78,11 @@
groups = allGroups.toArray(new IProtoLogGroup[0]);
}
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ try {
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
+ }
}
} else {
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 7bdcf2d..5d67534 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
+import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -106,18 +107,23 @@
final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
if (android.tracing.Flags.perfettoProtologTracing()) {
- File f = new File(sViewerConfigPath);
- if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
- // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
- // In some tests the viewer config file might not exist in which we don't
- // want to provide config path to the user
- Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
- + ProtoLogImpl.class.getSimpleName() + ". "
- + "Setting up without a viewer config instead...");
- sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
- } else {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ try {
+ File f = new File(sViewerConfigPath);
+ if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+ // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+ // In some tests the viewer config file might not exist in which we don't
+ // want to provide config path to the user
+ Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+ + ProtoLogImpl.class.getSimpleName() + ". "
+ + "Setting up without a viewer config instead...");
+
+ sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
+ } else {
+ sServiceInstance =
+ new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ }
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
}
} else {
var protologImpl = new LegacyProtoLogImpl(
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index bb654f0..d836563 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -62,6 +62,9 @@
// HWUI renders pipeline type: GL or Vulkan
optional PipelineType pipeline = 8;
+
+ // The UID of the app
+ optional int32 uid = 9;
}
message GraphicsStatsJankSummaryProto {
diff --git a/graphics/java/android/graphics/GraphicsStatsService.java b/graphics/java/android/graphics/GraphicsStatsService.java
index dc785c5..7a012bc 100644
--- a/graphics/java/android/graphics/GraphicsStatsService.java
+++ b/graphics/java/android/graphics/GraphicsStatsService.java
@@ -311,7 +311,7 @@
Log.w(TAG, "Unable to create path: '" + parent.getAbsolutePath() + "'");
return;
}
- nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mPackageName,
+ nSaveBuffer(path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
buffer.mData);
}
@@ -405,7 +405,7 @@
HistoricalBuffer buffer = buffers.get(i);
File path = pathForApp(buffer.mInfo);
skipFiles.add(path);
- nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mPackageName,
+ nAddToDump(dump, path.getAbsolutePath(), buffer.mInfo.mUid, buffer.mInfo.mPackageName,
buffer.mInfo.mVersionCode, buffer.mInfo.mStartTime, buffer.mInfo.mEndTime,
buffer.mData);
}
@@ -469,21 +469,23 @@
private static native int nGetAshmemSize();
private static native long nCreateDump(int outFd, boolean isProto);
- private static native void nAddToDump(long dump, String path, String packageName,
+ private static native void nAddToDump(long dump, String path, int uid, String packageName,
long versionCode, long startTime, long endTime, byte[] data);
private static native void nAddToDump(long dump, String path);
private static native void nFinishDump(long dump);
private static native void nFinishDumpInMemory(long dump, long pulledData, boolean lastFullDay);
- private static native void nSaveBuffer(String path, String packageName, long versionCode,
- long startTime, long endTime, byte[] data);
+ private static native void nSaveBuffer(String path, int uid, String packageName,
+ long versionCode, long startTime, long endTime, byte[] data);
private final class BufferInfo {
+ final int mUid;
final String mPackageName;
final long mVersionCode;
long mStartTime;
long mEndTime;
- BufferInfo(String packageName, long versionCode, long startTime) {
+ BufferInfo(int uid, String packageName, long versionCode, long startTime) {
+ this.mUid = uid;
this.mPackageName = packageName;
this.mVersionCode = versionCode;
this.mStartTime = startTime;
@@ -502,7 +504,7 @@
ActiveBuffer(IGraphicsStatsCallback token, int uid, int pid, String packageName,
long versionCode)
throws RemoteException, IOException {
- mInfo = new BufferInfo(packageName, versionCode, System.currentTimeMillis());
+ mInfo = new BufferInfo(uid, packageName, versionCode, System.currentTimeMillis());
mUid = uid;
mPid = pid;
mCallback = token;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7e6f434..4607a8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -584,7 +584,8 @@
final boolean windowModeChanged =
data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
- if (windowModeChanged || visibilityChanged) {
+ if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && visibilityChanged)) {
mRecentTasks.ifPresent(recentTasks ->
recentTasks.onTaskRunningInfoChanged(taskInfo));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e514dc3..f01ed84 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -39,6 +39,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -599,6 +600,18 @@
}
@Test
+ public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+ RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+ RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ task1_hidden.isVisible = false;
+
+ mOrganizer.onTaskInfoChanged(task1_hidden);
+
+ verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+ }
+
+ @Test
public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index 54369b9..80a8ae1 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -42,8 +42,9 @@
return reinterpret_cast<jlong>(dump);
}
-static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstring jpackage,
- jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
+static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jint uid,
+ jstring jpackage, jlong versionCode, jlong startTime, jlong endTime,
+ jbyteArray jdata) {
std::string path;
const ProfileData* data = nullptr;
LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null");
@@ -68,7 +69,8 @@
LOG_ALWAYS_FATAL_IF(!dump, "null passed for dump pointer");
const std::string package(packageChars.c_str(), packageChars.size());
- GraphicsStatsService::addToDump(dump, path, package, versionCode, startTime, endTime, data);
+ GraphicsStatsService::addToDump(dump, path, static_cast<uid_t>(uid), package, versionCode,
+ startTime, endTime, data);
}
static void addFileToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath) {
@@ -91,7 +93,7 @@
GraphicsStatsService::finishDumpInMemory(dump, data, lastFullDay == JNI_TRUE);
}
-static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
+static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jint uid, jstring jpackage,
jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
ScopedByteArrayRO buffer(env, jdata);
LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData),
@@ -106,7 +108,8 @@
const std::string path(pathChars.c_str(), pathChars.size());
const std::string package(packageChars.c_str(), packageChars.size());
const ProfileData* data = reinterpret_cast<const ProfileData*>(buffer.get());
- GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
+ GraphicsStatsService::saveBuffer(path, static_cast<uid_t>(uid), package, versionCode, startTime,
+ endTime, data);
}
static jobject gGraphicsStatsServiceObject = nullptr;
@@ -173,16 +176,16 @@
} // namespace android
using namespace android;
-static const JNINativeMethod sMethods[] =
- {{"nGetAshmemSize", "()I", (void*)getAshmemSize},
- {"nCreateDump", "(IZ)J", (void*)createDump},
- {"nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)addToDump},
- {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
- {"nFinishDump", "(J)V", (void*)finishDump},
- {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
- {"nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*)saveBuffer},
- {"nativeInit", "()V", (void*)nativeInit},
- {"nativeDestructor", "()V", (void*)nativeDestructor}};
+static const JNINativeMethod sMethods[] = {
+ {"nGetAshmemSize", "()I", (void*)getAshmemSize},
+ {"nCreateDump", "(IZ)J", (void*)createDump},
+ {"nAddToDump", "(JLjava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)addToDump},
+ {"nAddToDump", "(JLjava/lang/String;)V", (void*)addFileToDump},
+ {"nFinishDump", "(J)V", (void*)finishDump},
+ {"nFinishDumpInMemory", "(JJZ)V", (void*)finishDumpInMemory},
+ {"nSaveBuffer", "(Ljava/lang/String;ILjava/lang/String;JJJ[B)V", (void*)saveBuffer},
+ {"nativeInit", "()V", (void*)nativeInit},
+ {"nativeDestructor", "()V", (void*)nativeDestructor}};
int register_android_graphics_GraphicsStatsService(JNIEnv* env) {
jclass graphicsStatsService_class =
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 745393c..a6e786c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -58,6 +58,9 @@
// HWUI renders pipeline type: GL or Vulkan
optional PipelineType pipeline = 8;
+
+ // The UID of the app
+ optional int32 uid = 9;
}
message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index ece5905..702f2a5 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -22,6 +22,7 @@
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <inttypes.h>
#include <log/log.h>
+#include <stats_annotations.h>
#include <stats_event.h>
#include <statslog_hwui.h>
#include <sys/mman.h>
@@ -45,9 +46,9 @@
constexpr int sHistogramSize = ProfileData::HistogramSize();
constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
- int64_t versionCode, int64_t startTime, int64_t endTime,
- const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+ const std::string& package, int64_t versionCode,
+ int64_t startTime, int64_t endTime, const ProfileData* data);
static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
class FileDescriptor {
@@ -159,15 +160,16 @@
return success;
}
-bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
- int64_t versionCode, int64_t startTime, int64_t endTime,
- const ProfileData* data) {
+bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, uid_t uid,
+ const std::string& package, int64_t versionCode, int64_t startTime,
+ int64_t endTime, const ProfileData* data) {
if (proto->stats_start() == 0 || proto->stats_start() > startTime) {
proto->set_stats_start(startTime);
}
if (proto->stats_end() == 0 || proto->stats_end() < endTime) {
proto->set_stats_end(endTime);
}
+ proto->set_uid(static_cast<int32_t>(uid));
proto->set_package_name(package);
proto->set_version_code(versionCode);
proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
@@ -286,6 +288,7 @@
proto->package_name().c_str(), proto->has_summary());
return;
}
+ dprintf(fd, "\nUID: %d", proto->uid());
dprintf(fd, "\nPackage: %s", proto->package_name().c_str());
dprintf(fd, "\nVersion: %" PRId64, proto->version_code());
dprintf(fd, "\nStats since: %" PRId64 "ns", proto->stats_start());
@@ -319,14 +322,15 @@
dprintf(fd, "\n");
}
-void GraphicsStatsService::saveBuffer(const std::string& path, const std::string& package,
- int64_t versionCode, int64_t startTime, int64_t endTime,
- const ProfileData* data) {
+void GraphicsStatsService::saveBuffer(const std::string& path, uid_t uid,
+ const std::string& package, int64_t versionCode,
+ int64_t startTime, int64_t endTime, const ProfileData* data) {
protos::GraphicsStatsProto statsProto;
if (!parseFromFile(path, &statsProto)) {
statsProto.Clear();
}
- if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+ if (!mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime, endTime,
+ data)) {
return;
}
// Although we might not have read any data from the file, merging the existing data
@@ -383,7 +387,7 @@
private:
// use package name and app version for a key
- typedef std::pair<std::string, int64_t> DumpKey;
+ typedef std::tuple<uid_t, std::string, int64_t> DumpKey;
std::map<DumpKey, protos::GraphicsStatsProto> mStats;
int mFd;
@@ -392,7 +396,8 @@
};
void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
- auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+ auto dumpKey = std::make_tuple(static_cast<uid_t>(stat.uid()), stat.package_name(),
+ stat.version_code());
auto findIt = mStats.find(dumpKey);
if (findIt == mStats.end()) {
mStats[dumpKey] = stat;
@@ -437,15 +442,15 @@
return new Dump(outFd, type);
}
-void GraphicsStatsService::addToDump(Dump* dump, const std::string& path,
+void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, uid_t uid,
const std::string& package, int64_t versionCode,
int64_t startTime, int64_t endTime, const ProfileData* data) {
protos::GraphicsStatsProto statsProto;
if (!path.empty() && !parseFromFile(path, &statsProto)) {
statsProto.Clear();
}
- if (data &&
- !mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) {
+ if (data && !mergeProfileDataIntoProto(&statsProto, uid, package, versionCode, startTime,
+ endTime, data)) {
return;
}
if (!statsProto.IsInitialized()) {
@@ -556,6 +561,8 @@
// TODO: fill in UI mainline module version, when the feature is available.
AStatsEvent_writeInt64(event, (int64_t)0);
AStatsEvent_writeBool(event, !lastFullDay);
+ AStatsEvent_writeInt32(event, stat.uid());
+ AStatsEvent_addBoolAnnotation(event, ASTATSLOG_ANNOTATION_ID_IS_UID, true);
AStatsEvent_build(event);
}
delete dump;
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 4063f74..68c7355 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -44,13 +44,14 @@
ProtobufStatsd,
};
- static void saveBuffer(const std::string& path, const std::string& package, int64_t versionCode,
- int64_t startTime, int64_t endTime, const ProfileData* data);
+ static void saveBuffer(const std::string& path, uid_t uid, const std::string& package,
+ int64_t versionCode, int64_t startTime, int64_t endTime,
+ const ProfileData* data);
static Dump* createDump(int outFd, DumpType type);
- static void addToDump(Dump* dump, const std::string& path, const std::string& package,
- int64_t versionCode, int64_t startTime, int64_t endTime,
- const ProfileData* data);
+ static void addToDump(Dump* dump, const std::string& path, uid_t uid,
+ const std::string& package, int64_t versionCode, int64_t startTime,
+ int64_t endTime, const ProfileData* data);
static void addToDump(Dump* dump, const std::string& path);
static void finishDump(Dump* dump);
static void finishDumpInMemory(Dump* dump, AStatsEventList* data, bool lastFullDay);
diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
index c2d23e6..eb164f9 100644
--- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
+++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp
@@ -62,6 +62,7 @@
TEST(GraphicsStats, saveLoad) {
std::string path = findRootPath() + "/test_saveLoad";
+ uid_t uid = 123;
std::string packageName = "com.test.saveLoad";
MockProfileData mockData;
mockData.editJankFrameCount() = 20;
@@ -75,12 +76,13 @@
for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
}
- GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
protos::GraphicsStatsProto loadedProto;
EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
// Clean up the file
unlink(path.c_str());
+ EXPECT_EQ(uid, loadedProto.uid());
EXPECT_EQ(packageName, loadedProto.package_name());
EXPECT_EQ(5, loadedProto.version_code());
EXPECT_EQ(3000, loadedProto.stats_start());
@@ -109,6 +111,7 @@
TEST(GraphicsStats, merge) {
std::string path = findRootPath() + "/test_merge";
std::string packageName = "com.test.merge";
+ uid_t uid = 123;
MockProfileData mockData;
mockData.editJankFrameCount() = 20;
mockData.editTotalFrameCount() = 100;
@@ -121,7 +124,7 @@
for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
mockData.editSlowFrameCounts()[i] = (i % 5) + 1;
}
- GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData);
+ GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 3000, 7000, &mockData);
mockData.editJankFrameCount() = 50;
mockData.editTotalFrameCount() = 500;
for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) {
@@ -130,13 +133,15 @@
for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) {
mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2;
}
- GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData);
+
+ GraphicsStatsService::saveBuffer(path, uid, packageName, 5, 7050, 10000, &mockData);
protos::GraphicsStatsProto loadedProto;
EXPECT_TRUE(GraphicsStatsService::parseFromFile(path, &loadedProto));
// Clean up the file
unlink(path.c_str());
+ EXPECT_EQ(uid, loadedProto.uid());
EXPECT_EQ(packageName, loadedProto.package_name());
EXPECT_EQ(5, loadedProto.version_code());
EXPECT_EQ(3000, loadedProto.stats_start());
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec96..185f579 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -8,3 +8,10 @@
description: "Add media metrics for transcoding/editing events."
bug: "297487694"
}
+
+flag {
+ name: "stagefrightrecorder_enable_b_frames"
+ namespace: "media_solutions"
+ description: "Enable B frames for Stagefright recorder."
+ bug: "341121900"
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
new file mode 100644
index 0000000..16ca18a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 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="18dp"
+ android:height="18dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
new file mode 100644
index 0000000..3f75181
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/two_target_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6"
+ android:src="@drawable/settingslib_expressive_icon_chevron"/>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="40dp"
+ android:background="?android:attr/listDivider" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 3011ce0..b69912a 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-rc01"
+ extra["jetpackComposeVersion"] = "1.7.0"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 18a6db0..f942fd0 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,6 +26,8 @@
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
<!-- Footer text with two links. [DO NOT TRANSLATE] -->
<string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+ <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] -->
+ <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string>
<!-- Sample title -->
<string name="sample_title" translatable="false">Lorem ipsum</string>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657e..7139f5b4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,12 +42,15 @@
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
@@ -82,6 +85,7 @@
MainSwitchPreferencePageProvider,
ListPreferencePageProvider,
TwoTargetSwitchPreferencePageProvider,
+ ZeroStatePreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,
@@ -109,6 +113,8 @@
SuwScaffoldPageProvider,
BannerPageProvider,
CopyablePageProvider,
+ IntroPreferencePageProvider,
+ TopIntroPreferencePageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 0000000..603fcee
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "IntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("IntroPreference", owner)
+ .setUiLayoutFn { SampleIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678b..1626b02 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,9 @@
ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
.setLink(fromPage = owner).build(),
+ ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
new file mode 100644
index 0000000..b251266
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TopIntroPreference
+import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel
+
+private const val TITLE = "Sample TopIntroPreference"
+
+object TopIntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "TopIntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("TopIntroPreference", owner)
+ .setUiLayoutFn { SampleTopIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleTopIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = R.string.label_with_two_links
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
new file mode 100644
index 0000000..4a9c5c8
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ZeroStatePreference
+
+private const val TITLE = "Sample ZeroStatePreference"
+
+object ZeroStatePreferencePageProvider : SettingsPageProvider {
+ override val name = "ZeroStatePreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("ZeroStatePreference", owner)
+ .setUiLayoutFn {
+ SampleZeroStatePreference()
+ }.build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleZeroStatePreference() {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+ SettingsTheme {
+ ZeroStatePreferencePageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f0c2ea6..790aa9f 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,15 +54,16 @@
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.3.0-rc01")
+ api("androidx.compose.material3:material3:1.3.0")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-rc01")
+ api("androidx.navigation:navigation-compose:2.8.1")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
+ api("androidx.graphics:graphics-shapes-android:1.0.1")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:6.4.0")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e2425..f8c791a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,7 +21,9 @@
object SettingsDimension {
val paddingTiny = 2.dp
- val paddingSmall = 4.dp
+ val paddingExtraSmall = 4.dp
+ val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
+ val paddingExtraSmall5 = 10.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
@@ -56,6 +58,7 @@
val itemDividerHeight = 32.dp
val iconLarge = 48.dp
+ val introIconSize = 40.dp
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 15def72..f948d51 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -21,6 +21,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.util.SystemProperties
/**
* The Material 3 Theme for Settings.
@@ -41,4 +42,5 @@
}
}
-const val isSpaExpressiveEnabled = false
\ No newline at end of file
+val isSpaExpressiveEnabled
+ by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
new file mode 100644
index 0000000..ed4936b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.framework.util
+
+import android.annotation.SuppressLint
+import android.util.Log
+
+@SuppressLint("PrivateApi")
+object SystemProperties {
+ private const val TAG = "SystemProperties"
+
+ fun getBoolean(key: String, default: Boolean): Boolean = try {
+ val systemProperties = Class.forName("android.os.SystemProperties")
+ systemProperties
+ .getMethod("getBoolean", String::class.java, Boolean::class.java)
+ .invoke(systemProperties, key, default) as Boolean
+ } catch (e: Exception) {
+ Log.e(TAG, "getBoolean: $key", e)
+ default
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 0000000..22a5755
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ imageVector: ImageVector? = null,
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ appIcon: @Composable (() -> Unit),
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+ title: String,
+ descriptions: List<String>?,
+ icon: @Composable (() -> Unit),
+) {
+ Column(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingLarge,
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ icon()
+ IntroTitle(title)
+ IntroDescription(descriptions)
+ }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+ if (imageVector != null) {
+ Box(
+ modifier =
+ Modifier.size(SettingsDimension.itemIconContainerSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.secondaryContainer),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.introIconSize),
+ tint = MaterialTheme.colorScheme.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+ Box(
+ modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ appIcon()
+ }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+ Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+ if (descriptions != null) {
+ for (description in descriptions) {
+ if (description.isEmpty()) continue
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description", "Version"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
new file mode 100644
index 0000000..7e61959
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+/** The widget model for [TopIntroPreference] widget. */
+interface TopIntroPreferenceModel {
+ /** The content of this [TopIntroPreference]. */
+ val text: String
+
+ /** The text clicked to expand this [TopIntroPreference]. */
+ val expandText: String
+
+ /** The text clicked to collapse this [TopIntroPreference]. */
+ val collapseText: String
+
+ /** The text clicked to open other resources. Should be a resource Id. */
+ val labelText: Int?
+}
+
+@Composable
+fun TopIntroPreference(model: TopIntroPreferenceModel) {
+ var expanded by remember { mutableStateOf(false) }
+ Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
+ // TopIntroPreference content.
+ Column(
+ modifier =
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ .animateContentSize()
+ ) {
+ Text(
+ text = model.text,
+ style = MaterialTheme.typography.bodyLarge,
+ maxLines = if (expanded) MAX_LINE else MIN_LINE,
+ )
+ if (expanded) TopIntroAnnotatedText(model.labelText)
+ }
+
+ // TopIntroPreference collapse bar.
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { expanded = !expanded })
+ .padding(
+ top = SettingsDimension.paddingSmall,
+ bottom = SettingsDimension.paddingLarge,
+ start = SettingsDimension.paddingExtraLarge,
+ end = SettingsDimension.paddingExtraLarge,
+ ),
+ ) {
+ Icon(
+ imageVector =
+ if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
+ contentDescription = null,
+ modifier =
+ Modifier.size(SettingsDimension.itemIconSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.surfaceContainerHighest),
+ )
+ Text(
+ text = if (expanded) model.collapseText else model.expandText,
+ modifier = Modifier.padding(start = SettingsDimension.paddingSmall),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TopIntroAnnotatedText(@StringRes id: Int?) {
+ if (id != null) {
+ Box(
+ Modifier.padding(
+ top = SettingsDimension.paddingExtraSmall5,
+ bottom = SettingsDimension.paddingExtraSmall5,
+ end = SettingsDimension.paddingLarge,
+ )
+ ) {
+ Text(
+ text = annotatedStringResource(id),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun TopIntroPreferencePreview() {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label
+ }
+ )
+}
+
+const val MIN_LINE = 2
+const val MAX_LINE = 10
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
new file mode 100644
index 0000000..3f2e772
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.star
+import androidx.graphics.shapes.toPath
+
+@Composable
+fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
+ val zeroStateShape = remember {
+ RoundedPolygon.star(
+ numVerticesPerRadius = 6,
+ innerRadius = 0.75f,
+ rounding = CornerRounding(0.3f)
+ )
+ }
+ val clip = remember(zeroStateShape) {
+ RoundedPolygonShape(polygon = zeroStateShape)
+ }
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(
+ modifier = Modifier
+ .clip(clip)
+ .background(MaterialTheme.colorScheme.primary)
+ .size(160.dp)
+ ) {
+ Icon(
+ imageVector = icon,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(72.dp),
+ tint = MaterialTheme.colorScheme.onPrimary,
+ contentDescription = null,
+ )
+ }
+ if (text != null) {
+ Text(
+ text = text,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = 24.dp),
+ )
+ }
+ if (description != null) {
+ Box {
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ZeroStatePreferencePreview() {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+}
+
+class RoundedPolygonShape(
+ private val polygon: RoundedPolygon,
+ private var matrix: Matrix = Matrix()
+) : Shape {
+ private var path = Path()
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ path.rewind()
+ path = polygon.toPath().asComposePath()
+
+ matrix.reset()
+ matrix.scale(size.width / 2f, size.height / 2f)
+ matrix.translate(1f, 1f)
+ matrix.rotateZ(30.0f)
+
+ path.transform(matrix)
+ return Outline.Generic(path)
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index fb8f878..346f69b 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -28,5 +28,7 @@
<string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+ <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string>
+
<string name="test_link"><a href="https://www.android.com/">link</a></string>
</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
new file mode 100644
index 0000000..0827fa9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemPropertiesTest {
+
+ @Test
+ fun getBoolean_noCrash() {
+ SystemProperties.getBoolean("is_expressive_design_enabled", false)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 0000000..5d80145
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+ composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ val DESCRIPTION = listOf("Description", "")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
new file mode 100644
index 0000000..62a71d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TopIntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun content_collapsed_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun content_expended_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick()
+ composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TEXT = "Text"
+ const val EXPAND_TEXT = "Expand"
+ const val COLLAPSE_TEXT = "Collapse"
+ const val LABEL_TEXT = "Label"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
new file mode 100644
index 0000000..99ac27c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 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.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ZeroStatePreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE)
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION)
+ }
+
+ composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val DESCRIPTION = "Description"
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
new file mode 100644
index 0000000..4347ef2
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <include layout="@layout/settingslib_expressive_preference_icon_frame"/>
+
+ <include layout="@layout/settingslib_expressive_preference_text_frame" />
+
+ <include layout="@layout/settingslib_expressive_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index b125f71..58ff0ce 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,7 +72,10 @@
}
private void init(Context context) {
- setLayoutResource(R.layout.preference_two_target);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_two_target
+ : R.layout.preference_two_target;
+ setLayoutResource(resID);
mSmallIconSize = context.getResources().getDimensionPixelSize(
R.dimen.two_target_pref_small_icon_size);
mMediumIconSize = context.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index e41126f..2475c8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -31,6 +31,8 @@
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
+import com.android.settingslib.widget.SettingsThemeHelper;
+import com.android.settingslib.widget.theme.R;
/**
* A custom preference that provides inline switch toggle. It has a mandatory field for title, and
@@ -62,7 +64,9 @@
@Override
protected int getSecondTargetResId() {
- return androidx.preference.R.layout.preference_widget_switch_compat;
+ return SettingsThemeHelper.isExpressiveTheme(getContext())
+ ? R.layout.settingslib_expressive_preference_switch
+ : androidx.preference.R.layout.preference_widget_switch_compat;
}
@Override
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 492543f..af3ddfc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -310,6 +310,41 @@
.isEqualTo(displayId)
}
+ @Test
+ fun afterSuccessfulAuthentication_focusIsNotRequested() =
+ testScope.runTest {
+ val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
+ val textInputFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested)
+ lockDeviceAndOpenPasswordBouncer()
+
+ // remove focus from text field
+ underTest.onTextFieldFocusChanged(false)
+ runCurrent()
+
+ // focus should be requested
+ assertThat(textInputFocusRequested).isTrue()
+
+ // simulate text field getting focus
+ underTest.onTextFieldFocusChanged(true)
+ runCurrent()
+
+ // focus should not be requested anymore
+ assertThat(textInputFocusRequested).isFalse()
+
+ // authenticate successfully.
+ underTest.onPasswordInputChanged("password")
+ underTest.onAuthenticateKeyPressed()
+ runCurrent()
+
+ assertThat(authResult).isTrue()
+
+ // remove focus from text field
+ underTest.onTextFieldFocusChanged(false)
+ runCurrent()
+ // focus should not be requested again
+ assertThat(textInputFocusRequested).isFalse()
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
@@ -327,10 +362,7 @@
switchToScene(Scenes.Bouncer)
}
- private suspend fun TestScope.setLockout(
- isLockedOut: Boolean,
- failedAttemptCount: Int = 5,
- ) {
+ private suspend fun TestScope.setLockout(isLockedOut: Boolean, failedAttemptCount: Int = 5) {
if (isLockedOut) {
repeat(failedAttemptCount) {
kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
@@ -350,7 +382,7 @@
kosmos.fakeUserRepository.selectedUser.value =
SelectedUserModel(
userInfo = userInfo,
- selectionStatus = SelectionStatus.SELECTION_COMPLETE
+ selectionStatus = SelectionStatus.SELECTION_COMPLETE,
)
advanceTimeBy(PasswordBouncerViewModel.DELAY_TO_FETCH_IMES)
}
@@ -374,7 +406,7 @@
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
InputMethodModel.Subtype(subtypeId = it, isAuxiliary = it < auxiliarySubtypes)
- }
+ },
)
}
@@ -383,9 +415,6 @@
private const val WRONG_PASSWORD = "Wrong password"
private val USER_INFOS =
- listOf(
- UserInfo(100, "First user", 0),
- UserInfo(101, "Second user", 0),
- )
+ listOf(UserInfo(100, "First user", 0), UserInfo(101, "Second user", 0))
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
index b3ffc71..d6734e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.usage.UsageEvents
import android.content.pm.UserInfo
+import android.service.dream.dreamManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.activityStarter
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shared.system.taskStackChangeListeners
@@ -48,6 +50,7 @@
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
@@ -90,6 +93,27 @@
}
@Test
+ fun testNewTaskStartsWhileOnHub_stopsDream() =
+ testScope.runTest {
+ transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+ backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+ runCurrent()
+
+ verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+ moveTaskToFront()
+
+ argumentCaptor<OnDismissAction>().apply {
+ verify(activityStarter).dismissKeyguardThenExecute(capture(), anyOrNull(), any())
+
+ firstValue.onDismiss()
+ runCurrent()
+
+ // Dream is stopped once keyguard is dismissed.
+ verify(kosmos.dreamManager).stopDream()
+ }
+ }
+
+ @Test
fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() =
testScope.runTest {
transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
@@ -209,7 +233,7 @@
ownerName = "test",
),
),
- testScope
+ testScope,
)
runCurrent()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 873d1b3..4185aed 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -86,6 +86,9 @@
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
clearInput()
+ if (authenticationResult == AuthenticationResult.SUCCEEDED) {
+ onSuccessfulAuthentication()
+ }
}
awaitCancellation()
}
@@ -116,6 +119,9 @@
/** Returns the input entered so far. */
protected abstract fun getInput(): List<Any>
+ /** Invoked after a successful authentication. */
+ protected open fun onSuccessfulAuthentication() = Unit
+
/** Perform authentication result haptics */
private fun performAuthenticationHapticFeedback(result: AuthenticationResult) {
if (result == AuthenticationResult.SKIPPED) return
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 2493cf1..1427d78 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -81,50 +81,59 @@
val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
private val requests = Channel<Request>(Channel.BUFFERED)
+ private var wasSuccessfullyAuthenticated = false
override suspend fun onActivated(): Nothing {
- coroutineScope {
- launch { super.onActivated() }
- launch {
- requests.receiveAsFlow().collect { request ->
- when (request) {
- is OnImeSwitcherButtonClicked -> {
- inputMethodInteractor.showInputMethodPicker(
- displayId = request.displayId,
- showAuxiliarySubtypes = false,
- )
- }
- is OnImeDismissed -> {
- interactor.onImeHiddenByUser()
+ try {
+ coroutineScope {
+ launch { super.onActivated() }
+ launch {
+ requests.receiveAsFlow().collect { request ->
+ when (request) {
+ is OnImeSwitcherButtonClicked -> {
+ inputMethodInteractor.showInputMethodPicker(
+ displayId = request.displayId,
+ showAuxiliarySubtypes = false,
+ )
+ }
+ is OnImeDismissed -> {
+ interactor.onImeHiddenByUser()
+ }
}
}
}
+ launch {
+ combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+ hasInput && !hasFocus && !wasSuccessfullyAuthenticated
+ }
+ .collect { _isTextFieldFocusRequested.value = it }
+ }
+ launch {
+ selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it }
+ }
+ launch {
+ // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+ // whenever
+ // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+ combine(
+ // InputMethodManagerService sometimes takes
+ // some time to update its internal state when the
+ // selected user changes.
+ // As a workaround, delay fetching the IME info.
+ selectedUserInteractor.selectedUser.onEach {
+ delay(DELAY_TO_FETCH_IMES)
+ },
+ _isImeSwitcherButtonVisible.onSubscriberAdded(),
+ ) { selectedUserId, _ ->
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+ }
+ .collect { _isImeSwitcherButtonVisible.value = it }
+ }
+ awaitCancellation()
}
- launch {
- combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
- hasInput && !hasFocus
- }
- .collect { _isTextFieldFocusRequested.value = it }
- }
- launch { selectedUserInteractor.selectedUser.collect { _selectedUserId.value = it } }
- launch {
- // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
- // whenever
- // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
- combine(
- // InputMethodManagerService sometimes takes some time to update its
- // internal
- // state when the selected user changes. As a workaround, delay fetching the
- // IME
- // info.
- selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
- _isImeSwitcherButtonVisible.onSubscriberAdded()
- ) { selectedUserId, _ ->
- inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
- }
- .collect { _isImeSwitcherButtonVisible.value = it }
- }
- awaitCancellation()
+ } finally {
+ // reset whenever the view model is "deactivated"
+ wasSuccessfullyAuthenticated = false
}
}
@@ -141,6 +150,10 @@
return _password.value.toCharArray().toList()
}
+ override fun onSuccessfulAuthentication() {
+ wasSuccessfullyAuthenticated = true
+ }
+
/** Notifies that the user has changed the password input. */
fun onPasswordInputChanged(newPassword: String) {
if (newPassword.isNotEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
index f77dd58..f0f7ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSmartspaceRepository.kt
@@ -82,19 +82,26 @@
}
_timers.value =
- timerTargets.map { (stableId, target) ->
- CommunalSmartspaceTimer(
- // The view layer should have the instance based smartspaceTargetId instead of
- // stable id, so that when a new instance of the timer is created, for example,
- // when it is paused, the view should re-render its remote views.
- smartspaceTargetId =
- if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
- createdTimestampMillis = targetCreationTimes[stableId]!!,
- remoteViews = target.remoteViews!!,
- )
- }
-
- logger.d({ "Smartspace timers updated: $str1" }) { str1 = _timers.value.toString() }
+ timerTargets
+ .map { (stableId, target) ->
+ CommunalSmartspaceTimer(
+ // The view layer should have the instance based smartspaceTargetId instead
+ // of stable id, so that when a new instance of the timer is created, for
+ // example, when it is paused, the view should re-render its remote views.
+ smartspaceTargetId =
+ if (communalTimerFlickerFix()) stableId else target.smartspaceTargetId,
+ createdTimestampMillis = targetCreationTimes[stableId]!!,
+ remoteViews = target.remoteViews!!,
+ )
+ }
+ .also { newVal ->
+ // Only log when value changes to avoid filling up the buffer.
+ if (newVal != _timers.value) {
+ logger.d({ "Smartspace timers updated: $str1" }) {
+ str1 = newVal.toString()
+ }
+ }
+ }
}
override fun startListening() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
index 7453368..f7cd2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
@@ -16,10 +16,13 @@
package com.android.systemui.communal.domain.interactor
+import android.annotation.SuppressLint
import android.app.ActivityManager
+import android.app.DreamManager
import com.android.systemui.common.usagestats.domain.UsageStatsInteractor
import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
@@ -34,10 +37,12 @@
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
@@ -56,6 +61,8 @@
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val taskStackChangeListeners: TaskStackChangeListeners,
private val usageStatsInteractor: UsageStatsInteractor,
+ private val dreamManager: DreamManager,
+ @Background private val bgScope: CoroutineScope,
@CommunalLog logBuffer: LogBuffer,
) {
private companion object {
@@ -127,13 +134,21 @@
* Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it
* does. This can detect activities started due to broadcast trampolines from widgets.
*/
+ @SuppressLint("MissingPermission")
suspend fun waitForActivityStartAndDismissKeyguard() {
if (waitForActivityStartWhileOnHub()) {
logger.d("Detected trampoline, requesting unlock")
activityStarter.dismissKeyguardThenExecute(
- /* action= */ { false },
+ /* action= */ {
+ // Kill the dream when launching the trampoline activity. Right now the exit
+ // animation stalls when tapping the battery widget, and the dream remains
+ // visible until the transition hits some timeouts and gets cancelled.
+ // TODO(b/362841648): remove once exit animation is fixed.
+ bgScope.launch { dreamManager.stopDream() }
+ false
+ },
/* cancel= */ null,
- /* afterKeyguardGone= */ false
+ /* afterKeyguardGone= */ false,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 28db3b8..f90f02a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -70,7 +70,14 @@
) {
private val keyguardOccludedByApp: Flow<Boolean> =
if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ combine(
+ keyguardTransitionInteractor.currentKeyguardState,
+ communalSceneInteractor.isIdleOnCommunal,
+ ::Pair,
+ )
+ .map { (currentState, isIdleOnCommunal) ->
+ currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal
+ }
} else {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -120,7 +127,7 @@
// On fingerprint success when the screen is on and not dreaming, go to the home screen
fingerprintUnlockSuccessEvents
.sample(
- combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair),
+ combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
)
.collect { (interactive, dreaming) ->
if (interactive && !dreaming) {
@@ -148,7 +155,7 @@
}
},
/* cancel= */ null,
- /* afterKeyguardGone */ false
+ /* afterKeyguardGone */ false,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 77c54ec..3992c3f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -39,6 +39,7 @@
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
class HomeControlsDreamService
@Inject
@@ -53,7 +54,7 @@
) : DreamService() {
private val serviceJob = SupervisorJob()
- private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
+ private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService"))
private val logger = DreamLogger(logBuffer, TAG)
private lateinit var taskFragmentComponent: TaskFragmentComponent
private val wakeLock: WakeLock by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
index 7e2c9f8..4caf95b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.education.dagger
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
import com.android.systemui.contextualeducation.GestureType
@@ -56,7 +57,7 @@
fun provideEduDataStoreScope(
@Background bgDispatcher: CoroutineDispatcher
): CoroutineScope {
- return CoroutineScope(bgDispatcher + SupervisorJob())
+ return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope"))
}
@EduClock
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 0b8f741..cef9a4e 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.preview
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.app.WallpaperColors
import android.content.BroadcastReceiver
import android.content.Context
@@ -187,7 +188,7 @@
private var themeStyle: Style? = null
init {
- coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
+ coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job() + createCoroutineTracingContext("KeyguardPreviewRenderer"))
disposables += DisposableHandle { coroutineScope.cancel() }
clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData())
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4b62eab..0d55709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -179,14 +179,6 @@
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_START)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
- ),
- )
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
@@ -200,14 +192,6 @@
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_END)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
- ),
- )
/**
* Notifies that a slot with the given ID has been selected in the preview experience that is
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index c2b5d98..5559698 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -26,7 +26,7 @@
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.createCoroutineTracingContext
-import com.android.app.tracing.coroutines.launch
+import com.android.app.tracing.coroutines.traceCoroutine
import com.android.systemui.Flags.coroutineTracing
import com.android.systemui.util.Assert
import com.android.systemui.util.Compile
@@ -45,6 +45,7 @@
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
/**
* Runs the given [block] every time the [View] becomes attached (or immediately after calling this
@@ -137,7 +138,7 @@
): ViewLifecycleOwner {
return ViewLifecycleOwner(view).apply {
onCreate()
- lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
+ lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } }
}
}
@@ -367,7 +368,8 @@
* an extension function, and plumbing dagger-injected instances for static usage has little
* benefit.
*/
-private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + createCoroutineTracingContext()
+private val MAIN_DISPATCHER_SINGLETON =
+ Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached")
private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 8bec46a..70ca824 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -545,6 +545,7 @@
/** Bind this player view based on the data given. */
public void bindPlayer(@NonNull MediaData data, String key) {
+ SceneContainerFlag.assertInLegacyMode();
if (mMediaViewHolder == null) {
return;
}
@@ -638,10 +639,7 @@
// to something which might impact the measurement
// State refresh interferes with the translation animation, only run it if it's not running.
if (!mMetadataAnimationHandler.isRunning()) {
- // Don't refresh in scene framework, because it will calculate with invalid layout sizes
- if (!SceneContainerFlag.isEnabled()) {
- mMediaViewController.refreshState();
- }
+ mMediaViewController.refreshState();
}
if (shouldPlayTurbulenceNoise()) {
@@ -907,11 +905,6 @@
// Capture width & height from views in foreground for artwork scaling in background
int width = mMediaViewHolder.getAlbumView().getMeasuredWidth();
int height = mMediaViewHolder.getAlbumView().getMeasuredHeight();
- if (SceneContainerFlag.isEnabled() && (width <= 0 || height <= 0)) {
- // TODO(b/312714128): ensure we have a valid size before setting background
- width = mMediaViewController.getWidthInSceneContainerPx();
- height = mMediaViewController.getHeightInSceneContainerPx();
- }
final int finalWidth = width;
final int finalHeight = height;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 6440205..544dbdd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.mediaprojection.appselector
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.app.Activity
import android.content.ComponentName
import android.content.Context
@@ -133,7 +134,7 @@
@MediaProjectionAppSelector
@MediaProjectionAppSelectorScope
fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope =
- CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope"))
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b3c697e..1216a88 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -551,6 +551,12 @@
}
@Override
+ public void appTransitionStarting(int displayId, long startTime, long duration,
+ boolean forced) {
+ appTransitionPending(false);
+ }
+
+ @Override
public void appTransitionCancelled(int displayId) {
appTransitionPending(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
index d0437a7..b8f4ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.base.viewmodel
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -27,5 +28,5 @@
constructor(@Application private val applicationScope: CoroutineScope) {
fun create(): CoroutineScope =
- CoroutineScope(applicationScope.coroutineContext + SupervisorJob())
+ CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope"))
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 246fe38..ae56c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.tiles.dialog
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.util.Log
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
@@ -62,7 +63,7 @@
}
return
} else {
- coroutineScope = CoroutineScope(bgDispatcher)
+ coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope"))
dialog =
dialogFactory
.create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
index cca947f..ac75932 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.location.domain.interactor
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.content.Intent
import android.provider.Settings
import com.android.systemui.dagger.qualifiers.Application
@@ -52,7 +53,7 @@
val wasEnabled: Boolean = input.data.isEnabled
if (keyguardController.isMethodSecure() && keyguardController.isShowing()) {
activityStarter.postQSRunnableDismissingKeyguard {
- CoroutineScope(applicationScope.coroutineContext).launch {
+ CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch {
locationController.setLocationEnabled(!wasEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
index b25c61c..468e180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.saver.domain
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences
@@ -44,7 +45,7 @@
setTitle(R.string.data_saver_enable_title)
setMessage(R.string.data_saver_description)
setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ ->
- CoroutineScope(backgroundContext).launch {
+ CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch {
dataSaverController.setDataSaverEnabled(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 560028c..7b6a2cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,11 +444,9 @@
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
- if (mRunWithoutInterruptions) {
- enableAppearDrawing(false);
- }
// We need to reset the View state, even if the animation was cancelled
+ enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
if (mRunWithoutInterruptions) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index deae576..bad6f80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
@@ -114,7 +116,7 @@
private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> {
// Create a child scope so we can cancel it
- val vmScope = scope.createChildScope()
+ val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel"))
val vm =
MobileIconViewModel(
subId,
@@ -128,8 +130,8 @@
return Pair(vm, vmScope)
}
- private fun CoroutineScope.createChildScope() =
- CoroutineScope(coroutineContext + Job(coroutineContext[Job]))
+ private fun CoroutineScope.createChildScope(extraContext: CoroutineContext) =
+ CoroutineScope(coroutineContext + Job(coroutineContext[Job]) + extraContext)
private fun invalidateCaches(subIds: List<Int>) {
reuseCache.keys
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
index 8ecf250..2af84c7 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt
@@ -37,7 +37,7 @@
@Application
fun applicationScope(
@Main dispatcherContext: CoroutineContext,
- ): CoroutineScope = CoroutineScope(dispatcherContext)
+ ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope"))
@Provides
@Singleton
@@ -51,15 +51,7 @@
@Provides
@Singleton
@Main
- fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
- return Dispatchers.Main.immediate + tracingCoroutineContext
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- @Provides
- @Tracing
- @Singleton
- fun tracingCoroutineContext(): CoroutineContext {
- return createCoroutineTracingContext()
+ fun mainCoroutineContext(): CoroutineContext {
+ return Dispatchers.Main.immediate
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index a03221e..3c06828 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -91,10 +91,9 @@
@Background
@SysUISingleton
fun bgCoroutineContext(
- @Tracing tracingCoroutineContext: CoroutineContext,
@Background bgCoroutineDispatcher: CoroutineDispatcher,
): CoroutineContext {
- return bgCoroutineDispatcher + tracingCoroutineContext
+ return bgCoroutineDispatcher
}
/** Coroutine dispatcher for background operations on for UI. */
@@ -112,9 +111,8 @@
@UiBackground
@SysUISingleton
fun uiBgCoroutineContext(
- @Tracing tracingCoroutineContext: CoroutineContext,
@UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher,
): CoroutineContext {
- return uiBgCoroutineDispatcher + tracingCoroutineContext
+ return uiBgCoroutineDispatcher
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 82f41a7..4d9aaa6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.util.settings
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.annotation.UserIdInt
import android.content.ContentResolver
import android.database.ContentObserver
@@ -93,7 +94,7 @@
*/
@AnyThread
fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch {
registerContentObserverSync(getUriFor(name), settingsObserver)
}
@@ -110,7 +111,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch {
registerContentObserverSync(getUriFor(name), settingsObserver)
registered.run()
}
@@ -143,7 +144,7 @@
*/
@AnyThread
fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch {
registerContentObserverSync(uri, settingsObserver)
}
@@ -160,7 +161,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch {
registerContentObserverSync(uri, settingsObserver)
registered.run()
}
@@ -205,7 +206,7 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch {
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
}
@@ -223,7 +224,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch {
registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
registered.run()
}
@@ -274,7 +275,7 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
}
@@ -292,7 +293,7 @@
settingsObserver: ContentObserver,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch {
registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
registered.run()
}
@@ -329,7 +330,7 @@
*/
@AnyThread
fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) }
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) }
/**
* Look up a name in the database.
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 8e3b813..c820c07 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.util.settings
+import com.android.app.tracing.coroutines.createCoroutineTracingContext
import android.annotation.UserIdInt
import android.annotation.WorkerThread
import android.content.ContentResolver
@@ -78,7 +79,7 @@
}
override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userId)
}
@@ -112,7 +113,7 @@
notifyForDescendants: Boolean,
settingsObserver: ContentObserver
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch {
registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
}
@@ -157,7 +158,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch {
registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
}
@@ -198,7 +199,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
}
@@ -215,7 +216,7 @@
userHandle: Int,
@WorkerThread registered: Runnable
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch {
registerContentObserverForUserSync(uri, settingsObserver, userHandle)
registered.run()
}
@@ -274,7 +275,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) {
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch {
registerContentObserverForUserSync(
getUriFor(name),
notifyForDescendants,
@@ -338,7 +339,7 @@
settingsObserver: ContentObserver,
userHandle: Int
) =
- CoroutineScope(backgroundDispatcher).launch {
+ CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch {
registerContentObserverForUserSync(
uri,
notifyForDescendants,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
index 8124224..3d41362 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
@@ -16,9 +16,11 @@
package com.android.systemui.communal.domain.interactor
+import android.service.dream.dreamManager
import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.shared.system.taskStackChangeListeners
@@ -32,6 +34,8 @@
keyguardTransitionInteractor = keyguardTransitionInteractor,
taskStackChangeListeners = taskStackChangeListeners,
usageStatsInteractor = usageStatsInteractor,
+ dreamManager = dreamManager,
+ bgScope = applicationCoroutineScope,
logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"),
)
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334..c3b7087 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
package com.android.server.appfunctions;
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
- /** A map of per-user executors for queued work. */
- @GuardedBy("sLock")
- private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
- private static final Object sLock = new Object();
-
- /**
- * Returns a per-user executor for queued metadata sync request.
- *
- * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
- * the use of a single thread.
- *
- * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
- * MetadataSyncAdapter}.
- */
- // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
- public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
- synchronized (sLock) {
- ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
- if (executor == null) {
- executor = Executors.newSingleThreadExecutor();
- mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
- }
- return executor;
- }
- }
-
- /**
- * Shuts down and removes the per-user executor for queued work.
- *
- * <p>This should be called when the user is removed.
- */
- public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
- throws InterruptedException {
- ExecutorService executor;
- synchronized (sLock) {
- executor = mPerUserExecutorsLocked.get(user.getIdentifier());
- mPerUserExecutorsLocked.remove(user.getIdentifier());
- }
- if (executor != null) {
- executor.shutdown();
- var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
- }
- }
-
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index b4713d9..1e723b5 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@
public void onUserStopping(@NonNull TargetUser user) {
Objects.requireNonNull(user);
- try {
- AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
- MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
- } catch (InterruptedException e) {
- Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
- }
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
}
@Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e4..d84b205 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,7 +54,9 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +66,15 @@
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
- private final Executor mSyncExecutor;
+
+ private final ExecutorService mExecutor;
+
private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Future<?> mCurrentSyncTask;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
@@ -73,12 +82,10 @@
public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
- @NonNull Executor syncExecutor,
- @NonNull PackageManager packageManager,
- @NonNull AppSearchManager appSearchManager) {
- mSyncExecutor = Objects.requireNonNull(syncExecutor);
+ @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
+ mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -97,7 +104,7 @@
AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
.build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
- mSyncExecutor.execute(
+ Runnable runnable =
() -> {
try (FutureAppSearchSession staticMetadataSearchSession =
new FutureAppSearchSessionImpl(
@@ -117,10 +124,23 @@
} catch (Exception ex) {
settableSyncStatus.completeExceptionally(ex);
}
- });
+ };
+
+ synchronized (mLock) {
+ if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+ var unused = mCurrentSyncTask.cancel(false);
+ }
+ mCurrentSyncTask = mExecutor.submit(runnable);
+ }
+
return settableSyncStatus;
}
+ /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+ public void shutDown() {
+ mExecutor.shutdown();
+ }
+
@WorkerThread
@VisibleForTesting
void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527..e933ec1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@
PackageManager perUserPackageManager = userContext.getPackageManager();
if (perUserAppSearchManager != null) {
metadataSyncAdapter =
- new MetadataSyncAdapter(
- AppFunctionExecutors.getPerUserSyncExecutor(user),
- perUserPackageManager,
- perUserAppSearchManager);
+ new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
return metadataSyncAdapter;
}
@@ -74,7 +71,12 @@
*/
public static void removeUserSyncAdapter(UserHandle user) {
synchronized (sLock) {
- sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ MetadataSyncAdapter metadataSyncAdapter =
+ sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+ if (metadataSyncAdapter != null) {
+ metadataSyncAdapter.shutDown();
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ }
}
}
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 46d60f9..0c54720 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -454,10 +454,10 @@
@NonNull Associations associations)
throws IOException {
final XmlSerializer serializer = parent.startTag(null, XML_TAG_ASSOCIATIONS);
+ writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
for (AssociationInfo association : associations.getAssociations()) {
writeAssociation(serializer, association);
}
- writeIntAttribute(serializer, XML_ATTR_MAX_ID, associations.getMaxId());
serializer.endTag(null, XML_TAG_ASSOCIATIONS);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 871c320..414a4e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15304,10 +15304,8 @@
}
psr.setReportedForegroundServiceTypes(fgServiceTypes);
- ProcessChangeItem item = mProcessList.enqueueProcessChangeItemLocked(
- proc.getPid(), proc.info.uid);
- item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
- item.foregroundServiceTypes = fgServiceTypes;
+ mProcessList.enqueueProcessChangeItemLocked(proc.getPid(), proc.info.uid,
+ ProcessChangeItem.CHANGE_FOREGROUND_SERVICES, fgServiceTypes);
}
if (oomAdj) {
updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 22ec790..78a0a11 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -3617,14 +3617,12 @@
if (changes != 0) {
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
"Changes in " + app + ": " + changes);
- ActivityManagerService.ProcessChangeItem item =
- mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid);
- item.changes |= changes;
- item.foregroundActivities = state.hasRepForegroundActivities();
+ mProcessList.enqueueProcessChangeItemLocked(app.getPid(), app.info.uid,
+ changes, state.hasRepForegroundActivities());
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Item " + Integer.toHexString(System.identityHashCode(item))
- + " " + app.toShortString() + ": changes=" + item.changes
- + " foreground=" + item.foregroundActivities
+ "Enqueued process change item for "
+ + app.toShortString() + ": changes=" + changes
+ + " foreground=" + state.hasRepForegroundActivities()
+ " type=" + state.getAdjType() + " source=" + state.getAdjSource()
+ " target=" + state.getAdjTarget());
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 00250b4..a93ae72 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4998,53 +4998,70 @@
}
@GuardedBy("mService")
- ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+ void enqueueProcessChangeItemLocked(int pid, int uid, int changes, int foregroundServicetypes) {
synchronized (mProcessChangeLock) {
- int i = mPendingProcessChanges.size() - 1;
- ActivityManagerService.ProcessChangeItem item = null;
- while (i >= 0) {
- item = mPendingProcessChanges.get(i);
- if (item.pid == pid) {
- if (DEBUG_PROCESS_OBSERVERS) {
- Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
- }
- break;
- }
- i--;
- }
-
- if (i < 0) {
- // No existing item in pending changes; need a new one.
- final int num = mAvailProcessChanges.size();
- if (num > 0) {
- item = mAvailProcessChanges.remove(num - 1);
- if (DEBUG_PROCESS_OBSERVERS) {
- Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
- }
- } else {
- item = new ActivityManagerService.ProcessChangeItem();
- if (DEBUG_PROCESS_OBSERVERS) {
- Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
- }
- }
- item.changes = 0;
- item.pid = pid;
- item.uid = uid;
- if (mPendingProcessChanges.size() == 0) {
- if (DEBUG_PROCESS_OBSERVERS) {
- Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
- }
- mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
- .sendToTarget();
- }
- mPendingProcessChanges.add(item);
- }
-
- return item;
+ final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+ item.changes |= changes;
+ item.foregroundServiceTypes = foregroundServicetypes;
}
}
@GuardedBy("mService")
+ void enqueueProcessChangeItemLocked(int pid, int uid, int changes,
+ boolean hasForegroundActivities) {
+ synchronized (mProcessChangeLock) {
+ final ProcessChangeItem item = enqueueProcessChangeItemLocked(pid, uid);
+ item.changes |= changes;
+ item.foregroundActivities = hasForegroundActivities;
+ }
+ }
+
+ @GuardedBy({"mService", "mProcessChangeLock"})
+ private ProcessChangeItem enqueueProcessChangeItemLocked(int pid, int uid) {
+ int i = mPendingProcessChanges.size() - 1;
+ ActivityManagerService.ProcessChangeItem item = null;
+ while (i >= 0) {
+ item = mPendingProcessChanges.get(i);
+ if (item.pid == pid) {
+ if (DEBUG_PROCESS_OBSERVERS) {
+ Slog.i(TAG_PROCESS_OBSERVERS, "Re-using existing item: " + item);
+ }
+ break;
+ }
+ i--;
+ }
+
+ if (i < 0) {
+ // No existing item in pending changes; need a new one.
+ final int num = mAvailProcessChanges.size();
+ if (num > 0) {
+ item = mAvailProcessChanges.remove(num - 1);
+ if (DEBUG_PROCESS_OBSERVERS) {
+ Slog.i(TAG_PROCESS_OBSERVERS, "Retrieving available item: " + item);
+ }
+ } else {
+ item = new ActivityManagerService.ProcessChangeItem();
+ if (DEBUG_PROCESS_OBSERVERS) {
+ Slog.i(TAG_PROCESS_OBSERVERS, "Allocating new item: " + item);
+ }
+ }
+ item.changes = 0;
+ item.pid = pid;
+ item.uid = uid;
+ if (mPendingProcessChanges.size() == 0) {
+ if (DEBUG_PROCESS_OBSERVERS) {
+ Slog.i(TAG_PROCESS_OBSERVERS, "*** Enqueueing dispatch processes changed!");
+ }
+ mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+ .sendToTarget();
+ }
+ mPendingProcessChanges.add(item);
+ }
+
+ return item;
+ }
+
+ @GuardedBy("mService")
void scheduleDispatchProcessDiedLocked(int pid, int uid) {
synchronized (mProcessChangeLock) {
for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6ae6f3d..6af4be5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -619,7 +619,7 @@
this.op = op;
this.uid = uid;
this.uidState = uidState;
- this.packageName = packageName;
+ this.packageName = packageName.intern();
// We keep an invariant that the persistent device will always have an entry in
// mDeviceAttributedOps.
mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
@@ -1031,7 +1031,7 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
if (action.equals(ACTION_PACKAGE_ADDED)
@@ -1235,7 +1235,7 @@
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
SparseIntArray packageModes =
@@ -4739,7 +4739,7 @@
return null;
}
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
if (edit) {
@@ -5076,7 +5076,7 @@
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
+ uidState.pkgOps.put(pkgName.intern(), ops);
}
ops.put(op.op, op);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9e11081..df69afe 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10809,7 +10809,8 @@
//TODO move inside HardeningEnforcer after refactor that moves permission checks
// in the blockFocusMethod
if (permissionOverridesCheck) {
- mHardeningEnforcer.metricsLogFocusReq(/*blocked*/false, focusReqType, uid);
+ mHardeningEnforcer.metricsLogFocusReq(/*blocked*/ false, focusReqType, uid,
+ /*unblockedBySdk*/ false);
}
if (!permissionOverridesCheck && mHardeningEnforcer.blockFocusMethod(uid,
HardeningEnforcer.METHOD_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS,
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index faeba5d..6611110 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -168,6 +168,8 @@
}
boolean blocked = true;
+ // indicates the focus request was not blocked because of the SDK version
+ boolean unblockedBySdk = false;
if (noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, callingUid, packageName, attributionTag)) {
if (DEBUG) {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking");
@@ -179,9 +181,10 @@
+ targetSdk);
}
blocked = false;
+ unblockedBySdk = true;
}
- metricsLogFocusReq(blocked, focusReqType, callingUid);
+ metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk);
if (!blocked) {
return false;
@@ -195,7 +198,16 @@
return true;
}
- /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid) {
+ /**
+ * Log metrics for the focus request
+ * @param blocked true if the call blocked
+ * @param focusReq the type of focus request
+ * @param callingUid the UID of the caller
+ * @param unblockedBySdk if blocked is false,
+ * true indicates it was unblocked thanks to an older SDK
+ */
+ /*package*/ void metricsLogFocusReq(boolean blocked, int focusReq, int callingUid,
+ boolean unblockedBySdk) {
final String metricId = blocked ? METRIC_COUNTERS_FOCUS_DENIAL.get(focusReq)
: METRIC_COUNTERS_FOCUS_GRANT.get(focusReq);
if (TextUtils.isEmpty(metricId)) {
@@ -204,6 +216,12 @@
}
try {
Counter.logIncrementWithUid(metricId, callingUid);
+ if (!blocked && unblockedBySdk) {
+ // additional metric to capture focus requests that are currently granted
+ // because the app is on an older SDK, but would have been blocked otherwise
+ Counter.logIncrementWithUid(
+ "media_audio.value_audio_focus_grant_hardening_waived_by_sdk", callingUid);
+ }
} catch (Exception e) {
Slog.e(TAG, "Counter error metricId:" + metricId + " for focus req:" + focusReq
+ " from uid:" + callingUid, e);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e7fd8f7..ae33b83 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -571,6 +571,10 @@
private final DisplayNotificationManager mDisplayNotificationManager;
private final ExternalDisplayStatsService mExternalDisplayStatsService;
+ // Manages the relative placement of extended displays
+ @Nullable
+ private final DisplayTopologyCoordinator mDisplayTopologyCoordinator;
+
/**
* Applications use {@link android.view.Display#getRefreshRate} and
* {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -644,6 +648,11 @@
mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
mExternalDisplayStatsService);
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
+ if (mFlags.isDisplayTopologyEnabled()) {
+ mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+ } else {
+ mDisplayTopologyCoordinator = null;
+ }
}
public void setupSchedulerPolicies() {
@@ -3474,9 +3483,13 @@
mSmallAreaDetectionController.dump(pw);
}
+ if (mDisplayTopologyCoordinator != null) {
+ pw.println();
+ mDisplayTopologyCoordinator.dump(pw);
+ }
+
pw.println();
mFlags.dump(pw);
-
}
private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
new file mode 100644
index 0000000..631f147
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 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.display;
+
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class manages the relative placement (topology) of extended displays. It is responsible for
+ * updating and persisting the topology.
+ */
+class DisplayTopologyCoordinator {
+
+ /**
+ * The topology tree
+ */
+ @Nullable
+ private TopologyTreeNode mRoot;
+
+ /**
+ * The logical display ID of the primary display that will show certain UI elements.
+ * This is not necessarily the same as the default display.
+ */
+ private int mPrimaryDisplayId;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param pw The stream to dump information to.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("DisplayTopologyCoordinator:");
+ pw.println("--------------------");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+
+ ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
+
+ ipw.println("Topology tree:");
+ if (mRoot != null) {
+ ipw.increaseIndent();
+ mRoot.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ private static class TopologyTreeNode {
+
+ /**
+ * The logical display ID
+ */
+ private int mDisplayId;
+
+ private final List<TopologyTreeNode> mChildren = new ArrayList<>();
+
+ /**
+ * The position of this display relative to its parent.
+ */
+ private Position mPosition;
+
+ /**
+ * The distance from the top edge of the parent display to the top edge of this display (in
+ * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
+ * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
+ * used is density-independent pixels (dp).
+ */
+ private double mOffset;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param ipw The stream to dump information to.
+ */
+ void dump(IndentingPrintWriter ipw) {
+ ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
+ + ", offset=" + mOffset + "}");
+ ipw.increaseIndent();
+ for (TopologyTreeNode child : mChildren) {
+ child.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ private enum Position {
+ POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f600e7f..df66893 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -69,6 +69,10 @@
Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
Flags::enableModeLimitForExternalDisplay);
+ private final FlagState mDisplayTopology = new FlagState(
+ Flags.FLAG_DISPLAY_TOPOLOGY,
+ Flags::displayTopology);
+
private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
Flags::enableConnectedDisplayErrorHandling);
@@ -266,6 +270,10 @@
return mExternalDisplayLimitModeState.isEnabled();
}
+ public boolean isDisplayTopologyEnabled() {
+ return mDisplayTopology.isEnabled();
+ }
+
/**
* @return Whether displays refresh rate synchronization is enabled.
*/
@@ -441,6 +449,7 @@
pw.println(" " + mConnectedDisplayManagementFlagState);
pw.println(" " + mDisplayOffloadFlagState);
pw.println(" " + mExternalDisplayLimitModeState);
+ pw.println(" " + mDisplayTopology);
pw.println(" " + mHdrClamperFlagState);
pw.println(" " + mNbmControllerFlagState);
pw.println(" " + mPowerThrottlingClamperFlagState);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9968ba5..e3ebe5b 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -92,6 +92,14 @@
}
flag {
+ name: "display_topology"
+ namespace: "display_manager"
+ description: "Display topology for moving cursors and windows between extended displays"
+ bug: "278199220"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_displays_refresh_rates_synchronization"
namespace: "display_manager"
description: "Enables synchronization of refresh rates across displays"
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
index 7ba7769..82b36af 100644
--- a/services/core/java/com/android/server/input/KeyRemapper.java
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -17,27 +17,24 @@
package com.android.server.input;
import android.content.Context;
-import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.ArrayMap;
import android.util.FeatureFlagUtils;
-import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import java.util.Map;
-import java.util.Objects;
/**
* A component of {@link InputManagerService} responsible for managing key remappings.
*
* @hide
*/
-final class KeyRemapper implements InputManager.InputDeviceListener {
+final class KeyRemapper {
- private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+ private static final int MSG_UPDATE_EXISTING_KEY_REMAPPING = 1;
private static final int MSG_REMAP_KEY = 2;
private static final int MSG_CLEAR_ALL_REMAPPING = 3;
@@ -49,7 +46,7 @@
private final Handler mHandler;
KeyRemapper(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper) {
+ PersistentDataStore dataStore, Looper looper) {
mContext = context;
mNative = nativeService;
mDataStore = dataStore;
@@ -57,13 +54,7 @@
}
public void systemRunning() {
- InputManager inputManager = Objects.requireNonNull(
- mContext.getSystemService(InputManager.class));
- inputManager.registerInputDeviceListener(this, mHandler);
-
- Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
- inputManager.getInputDeviceIds());
- mHandler.sendMessage(msg);
+ Message.obtain(mHandler, MSG_UPDATE_EXISTING_KEY_REMAPPING).sendToTarget();
}
public void remapKey(int fromKey, int toKey) {
@@ -91,19 +82,19 @@
}
}
- private void addKeyRemapping(int fromKey, int toKey) {
- InputManager inputManager = Objects.requireNonNull(
- mContext.getSystemService(InputManager.class));
- for (int deviceId : inputManager.getInputDeviceIds()) {
- InputDevice inputDevice = inputManager.getInputDevice(deviceId);
- if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
- mNative.addKeyRemapping(deviceId, fromKey, toKey);
- }
+ private void setKeyRemapping(Map<Integer, Integer> keyRemapping) {
+ int index = 0;
+ int[] fromKeycodesArr = new int[keyRemapping.size()];
+ int[] toKeycodesArr = new int[keyRemapping.size()];
+ for (Map.Entry<Integer, Integer> entry : keyRemapping.entrySet()) {
+ fromKeycodesArr[index] = entry.getKey();
+ toKeycodesArr[index] = entry.getValue();
+ index++;
}
+ mNative.setKeyRemapping(fromKeycodesArr, toKeycodesArr);
}
private void remapKeyInternal(int fromKey, int toKey) {
- addKeyRemapping(fromKey, toKey);
synchronized (mDataStore) {
try {
if (fromKey == toKey) {
@@ -114,6 +105,7 @@
} finally {
mDataStore.saveIfNeeded();
}
+ setKeyRemapping(mDataStore.getKeyRemapping());
}
}
@@ -123,45 +115,25 @@
Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
for (int fromKey : keyRemapping.keySet()) {
mDataStore.clearMappedKey(fromKey);
-
- // Remapping to itself will clear the remapping on native side
- addKeyRemapping(fromKey, fromKey);
}
} finally {
mDataStore.saveIfNeeded();
}
+ setKeyRemapping(mDataStore.getKeyRemapping());
}
}
- @Override
- public void onInputDeviceAdded(int deviceId) {
+ public void updateExistingKeyMapping() {
if (!supportRemapping()) {
return;
}
- InputManager inputManager = Objects.requireNonNull(
- mContext.getSystemService(InputManager.class));
- InputDevice inputDevice = inputManager.getInputDevice(deviceId);
- if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
- Map<Integer, Integer> remapping = getKeyRemapping();
- remapping.forEach(
- (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
- }
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- }
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
+ setKeyRemapping(getKeyRemapping());
}
private boolean handleMessage(Message msg) {
switch (msg.what) {
- case MSG_UPDATE_EXISTING_DEVICES:
- for (int deviceId : (int[]) msg.obj) {
- onInputDeviceAdded(deviceId);
- }
+ case MSG_UPDATE_EXISTING_KEY_REMAPPING:
+ updateExistingKeyMapping();
return true;
case MSG_REMAP_KEY:
remapKeyInternal(msg.arg1, msg.arg2);
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5dd461d..d17e256 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -48,7 +48,7 @@
int getSwitchState(int deviceId, int sourceMask, int sw);
- void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+ void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
@@ -311,7 +311,7 @@
public native int getSwitchState(int deviceId, int sourceMask, int sw);
@Override
- public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+ public native void setKeyRemapping(int[] fromKeyCodes, int[] toKeyCodes);
@Override
public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index 0e940d2..f351465 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -52,6 +52,8 @@
private static final float DEFAULT_RES_Y = 45f;
private static final int TEXT_PADDING_DP = 12;
private static final int ROUNDED_CORNER_RADIUS_DP = 24;
+ private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99);
+ private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169);
/**
* Input device ID for the touchpad that this debug view is displaying.
@@ -75,6 +77,8 @@
private int mWindowLocationBeforeDragY;
private int mLatestGestureType = 0;
private TextView mGestureInfoView;
+ private TextView mNameView;
+
@NonNull
private TouchpadHardwareState mLastTouchpadState =
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
@@ -119,35 +123,34 @@
LayoutParams.WRAP_CONTENT));
setBackgroundColor(Color.TRANSPARENT);
- TextView nameView = new TextView(context);
- nameView.setBackgroundColor(Color.RED);
- nameView.setTextSize(TEXT_SIZE_SP);
- nameView.setText(Objects.requireNonNull(Objects.requireNonNull(
+ mNameView = new TextView(context);
+ mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+ mNameView.setTextSize(TEXT_SIZE_SP);
+ mNameView.setText(Objects.requireNonNull(Objects.requireNonNull(
mContext.getSystemService(InputManager.class))
.getInputDevice(touchpadId)).getName());
- nameView.setGravity(Gravity.CENTER);
- nameView.setTextColor(Color.WHITE);
+ mNameView.setGravity(Gravity.CENTER);
+ mNameView.setTextColor(Color.WHITE);
int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
getResources().getDisplayMetrics());
- nameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
- nameView.setLayoutParams(
+ mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
+ mNameView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mTouchpadVisualizationView = new TouchpadVisualizationView(context,
mTouchpadHardwareProperties);
- mTouchpadVisualizationView.setBackgroundColor(Color.WHITE);
mGestureInfoView = new TextView(context);
- mGestureInfoView.setBackgroundColor(Color.BLACK);
mGestureInfoView.setTextSize(TEXT_SIZE_SP);
mGestureInfoView.setText("Latest Gesture: ");
mGestureInfoView.setGravity(Gravity.CENTER);
- mGestureInfoView.setTextColor(Color.WHITE);
mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
mGestureInfoView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- addView(nameView);
+ updateTheme(getResources().getConfiguration().uiMode);
+
+ addView(mNameView);
addView(mTouchpadVisualizationView);
addView(mGestureInfoView);
@@ -239,6 +242,8 @@
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
+
+ updateTheme(newConfig.uiMode);
updateScreenDimensions();
updateViewsDimensions();
@@ -250,6 +255,27 @@
mWindowManager.updateViewLayout(this, mWindowLayoutParams);
}
+ private void updateTheme(int uiMode) {
+ int currentNightMode = uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ if (currentNightMode == Configuration.UI_MODE_NIGHT_YES) {
+ setNightModeTheme();
+ } else {
+ setLightModeTheme();
+ }
+ }
+
+ private void setLightModeTheme() {
+ mTouchpadVisualizationView.setLightModeTheme();
+ mGestureInfoView.setBackgroundColor(Color.WHITE);
+ mGestureInfoView.setTextColor(Color.BLACK);
+ }
+
+ private void setNightModeTheme() {
+ mTouchpadVisualizationView.setNightModeTheme();
+ mGestureInfoView.setBackgroundColor(Color.BLACK);
+ mGestureInfoView.setTextColor(Color.WHITE);
+ }
+
private boolean isSlopExceeded(float deltaX, float deltaY) {
return deltaX * deltaX + deltaY * deltaY >= mTouchSlop * mTouchSlop;
}
@@ -333,12 +359,12 @@
private void onTouchpadButtonPress() {
Slog.d(TAG, "You clicked me!");
- getChildAt(0).setBackgroundColor(Color.BLUE);
+ mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
}
private void onTouchpadButtonRelease() {
Slog.d(TAG, "You released the click");
- getChildAt(0).setBackgroundColor(Color.RED);
+ mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
}
/**
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 2eed9ba..96426bb 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.Slog;
@@ -58,11 +59,9 @@
mScaleFactor = 1;
mOvalStrokePaint = new Paint();
mOvalStrokePaint.setAntiAlias(true);
- mOvalStrokePaint.setARGB(255, 0, 0, 0);
mOvalStrokePaint.setStyle(Paint.Style.STROKE);
mOvalFillPaint = new Paint();
mOvalFillPaint.setAntiAlias(true);
- mOvalFillPaint.setARGB(255, 0, 0, 0);
mTracePaint = new Paint();
mTracePaint.setAntiAlias(false);
mTracePaint.setARGB(255, 0, 0, 255);
@@ -195,6 +194,24 @@
mScaleFactor = scaleFactor;
}
+ /**
+ * Change the colors of the objects inside the view to light mode theme.
+ */
+ public void setLightModeTheme() {
+ this.setBackgroundColor(Color.rgb(20, 20, 20));
+ mOvalFillPaint.setARGB(255, 255, 255, 255);
+ mOvalStrokePaint.setARGB(255, 255, 255, 255);
+ }
+
+ /**
+ * Change the colors of the objects inside the view to night mode theme.
+ */
+ public void setNightModeTheme() {
+ this.setBackgroundColor(Color.rgb(240, 240, 240));
+ mOvalFillPaint.setARGB(255, 0, 0, 0);
+ mOvalStrokePaint.setARGB(255, 0, 0, 0);
+ }
+
private float translateX(float x) {
return translateRange(mTouchpadHardwareProperties.getLeft(),
mTouchpadHardwareProperties.getRight(), 0, getWidth(), x);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3e70d92..8bab9de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1101,8 +1101,6 @@
if (android.multiuser.Flags.cachesNotInvalidatedAtStartReadOnly()) {
UserManager.invalidateIsUserUnlockedCache();
UserManager.invalidateQuietModeEnabledCache();
- UserManager.invalidateStaticUserProperties();
- UserManager.invalidateUserPropertiesCache();
UserManager.invalidateUserSerialNumberCache();
}
}
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 46207c1..b43ddaa 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -572,7 +572,7 @@
return false;
}
- // First check if the user started on display
+ // First check if the user is assigned to a display
int userAssignedToDisplay = getUserStartedOnDisplay(displayId);
if (userAssignedToDisplay != USER_NULL) {
Slogf.w(TAG, "assignUserToExtraDisplay(%d, %d): failed because display was assigned"
@@ -918,10 +918,16 @@
if (!isStartedVisibleProfileLocked(userId)) {
return userId;
} else if (DBG) {
- Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
- + "a profile", displayId, userId);
+ Slogf.d(TAG,
+ "getUserAssignedToDisplay(%d): skipping user %d because it's a profile",
+ displayId, userId);
}
}
+ int userAssignedToExtraDisplay = mExtraDisplaysAssignedToUsers.get(displayId,
+ USER_NULL);
+ if (userAssignedToExtraDisplay != USER_NULL) {
+ return userAssignedToExtraDisplay;
+ }
}
if (!returnCurrentUserByDefault) {
if (DBG) {
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index 20184e9f..f69a017 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -16,13 +16,19 @@
package com.android.server.power;
+import android.app.AlarmManager;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
+import android.app.IAlarmManager;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ShellCommand;
+import android.os.SystemClock;
import android.util.SparseArray;
import android.view.Display;
@@ -34,12 +40,26 @@
private final Context mContext;
private final PowerManagerService.BinderService mService;
+ private final IAlarmListener mAlarmListener;
+ private IAlarmManager mAlarmManager;
private SparseArray<WakeLock> mProxWakelocks = new SparseArray<>();
PowerManagerShellCommand(Context context, PowerManagerService.BinderService service) {
mContext = context;
mService = service;
+ mAlarmManager =
+ IAlarmManager.Stub.asInterface(ServiceManager.getService(Context.ALARM_SERVICE));
+ mAlarmListener = new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ mService.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "PowerManagerShellCommand",
+ mContext.getOpPackageName());
+ }
+ };
}
@Override
@@ -65,6 +85,10 @@
return runSetProx();
case "set-face-down-detector":
return runSetFaceDownDetector();
+ case "sleep":
+ return runSleep();
+ case "wakeup":
+ return runWakeUp();
default:
return handleDefaultCommands(cmd);
}
@@ -194,6 +218,70 @@
return 0;
}
+ private int runSleep() {
+ try {
+ mService.goToSleep(
+ SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+ PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+ } catch (Exception e) {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("Error: " + e);
+ return -1;
+ }
+ return 0;
+ }
+
+ private int runWakeUp() {
+ final PrintWriter pw = getOutPrintWriter();
+ String delay = getNextArg();
+ if (delay == null) {
+ try {
+ mService.wakeUp(
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "PowerManagerShellCommand",
+ mContext.getOpPackageName());
+ } catch (Exception e) {
+ pw.println("Error: " + e);
+ return -1;
+ }
+ } else {
+ long delayMillis;
+ try {
+ delayMillis = Long.parseLong(delay);
+ } catch (NumberFormatException e) {
+ pw.println("Error: Can't parse arg " + delay + " as a long: " + e);
+ return -1;
+ }
+ if (delayMillis < 0) {
+ pw.println("Error: Can't set a negative delay: " + delayMillis);
+ return -1;
+ }
+ long wakeUpTime = System.currentTimeMillis() + delayMillis;
+ if (mAlarmManager == null) {
+ // PowerManagerShellCommand may be initialized before AlarmManagerService
+ // is brought up. Make sure mAlarmManager exists.
+ mAlarmManager = IAlarmManager.Stub.asInterface(
+ ServiceManager.getService(Context.ALARM_SERVICE));
+ }
+ try {
+ // This command is called by the shell, which has "com.android.shell" as package
+ // name.
+ pw.println("Schedule an alarm to wakeup in "
+ + delayMillis + " ms, on behalf of shell.");
+ mAlarmManager.set("com.android.shell",
+ AlarmManager.RTC_WAKEUP, wakeUpTime,
+ 0, 0, AlarmManager.FLAG_PRIORITIZE,
+ null, mAlarmListener, "PowerManagerShellCommand", null, null);
+ } catch (Exception e) {
+ pw.println("Error: " + e);
+ return -1;
+ }
+ }
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -221,6 +309,11 @@
pw.println(" created by set-prox including their held status.");
pw.println(" set-face-down-detector [true|false]");
pw.println(" sets whether we use face down detector timeouts or not");
+ pw.println(" sleep");
+ pw.println(" requests to sleep the device");
+ pw.println(" wakeup <delay>");
+ pw.println(" requests to wake up the device. If a delay of milliseconds is specified,");
+ pw.println(" alarm manager will schedule a wake up after the delay.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index e3d71e4..f78c448 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -81,7 +81,7 @@
public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
- private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+ private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -610,14 +610,16 @@
}
};
+ String intentActionName = CLASS_NAME + rollback.getRollbackId();
// Register the BroadcastReceiver
mContext.registerReceiver(rollbackReceiver,
- new IntentFilter(ACTION_NAME),
+ new IntentFilter(intentActionName),
Context.RECEIVER_NOT_EXPORTED);
- Intent intentReceiver = new Intent(ACTION_NAME);
+ Intent intentReceiver = new Intent(intentActionName);
intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
intentReceiver.setPackage(mContext.getPackageName());
+ intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
rollback.getRollbackId(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccc9b17..12d733f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,9 +2641,15 @@
return true;
}
// Only do transfer after transaction has done when starting window exist.
- if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
- mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
- return true;
+ if (mStartingData != null) {
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+ return true;
+ }
}
requestCopySplashScreen();
return isTransferringSplashScreen();
@@ -2847,7 +2853,11 @@
final boolean animate;
final boolean hasImeSurface;
if (mStartingData != null) {
- if (mStartingData.mWaitForSyncTransactionCommit
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit
|| mSyncState != SYNC_STATE_NONE) {
mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb207..896612d 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -68,7 +68,9 @@
* window.
* Note this isn't equal to transition playing, the period should be
* Sync finishNow -> Start transaction apply.
+ * @deprecated TODO(b/362347290): cleanup after fix ramp up
*/
+ @Deprecated
boolean mWaitForSyncTransactionCommit;
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a2fda0a..86bb75a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,6 +35,8 @@
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -49,6 +51,7 @@
import static android.view.SurfaceControl.METADATA_TASK_ID;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -133,6 +136,7 @@
import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -503,6 +507,12 @@
int mOffsetXForInsets;
int mOffsetYForInsets;
+ /**
+ * Whether the compatibility overrides that change the resizability of the app should be allowed
+ * for the specific app.
+ */
+ boolean mAllowForceResizeOverride = true;
+
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -666,6 +676,7 @@
intent = _intent;
mMinWidth = minWidth;
mMinHeight = minHeight;
+ updateAllowForceResizeOverride();
}
mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
@@ -1028,6 +1039,7 @@
mTaskSupervisor.mRecentTasks.remove(this);
mTaskSupervisor.mRecentTasks.add(this);
}
+ updateAllowForceResizeOverride();
}
/** Sets the original minimal width and height. */
@@ -1823,6 +1835,17 @@
-1 /* don't check PID */, -1 /* don't check UID */, this);
}
+ private void updateAllowForceResizeOverride() {
+ try {
+ mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getProperty(
+ PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES,
+ getBasePackageName()).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ // Package not found or property not defined, reset to default value.
+ mAllowForceResizeOverride = true;
+ }
+ }
+
/**
* Check that a given bounds matches the application requested orientation.
*
@@ -2812,7 +2835,18 @@
boolean isResizeable(boolean checkPictureInPictureSupport) {
final boolean forceResizable = mAtmService.mForceResizableActivities
&& getActivityType() == ACTIVITY_TYPE_STANDARD;
- return forceResizable || ActivityInfo.isResizeableMode(mResizeMode)
+ if (forceResizable) return true;
+
+ final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId);
+ final boolean forceResizableOverride = mAllowForceResizeOverride
+ && CompatChanges.isChangeEnabled(
+ FORCE_RESIZE_APP, getBasePackageName(), userHandle);
+ final boolean forceNonResizableOverride = mAllowForceResizeOverride
+ && CompatChanges.isChangeEnabled(
+ FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle);
+
+ if (forceNonResizableOverride) return false;
+ return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode)
|| (mSupportsPictureInPicture && checkPictureInPictureSupport);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 92953e5..83e714d 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -429,7 +429,7 @@
}
final IBinder activityToken;
- if (activity.getPid() == mOrganizerPid) {
+ if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) {
// We only pass the actual token if the activity belongs to the organizer process.
activityToken = activity.token;
} else {
@@ -458,7 +458,8 @@
change.setTaskFragmentToken(lastParentTfToken);
}
// Only pass the activity token to the client if it belongs to the same process.
- if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
+ if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid
+ && nextFillTaskActivity.getUid() == mOrganizerUid) {
change.setOtherActivityToken(nextFillTaskActivity.token);
}
return change;
@@ -553,6 +554,10 @@
"Replacing existing organizer currently unsupported");
}
+ if (pid <= 0) {
+ throw new IllegalStateException("Cannot register from invalid pid: " + pid);
+ }
+
if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0a9cb1c..1c03ba5 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4351,4 +4351,7 @@
t.merge(mSyncTransaction);
}
+ int getSyncTransactionCommitCallbackDepth() {
+ return mSyncTransactionCommitCallbackDepth;
+ }
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index d2493c5..5cd117b 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -357,6 +357,7 @@
FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId);
void setStylusPointerIconEnabled(bool enabled);
void setInputMethodConnectionIsActive(bool isActive);
+ void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
/* --- InputReaderPolicyInterface implementation --- */
@@ -504,6 +505,9 @@
// True if there is an active input method connection.
bool isInputMethodConnectionActive{false};
+
+ // Keycodes to be remapped.
+ std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -761,6 +765,8 @@
outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
+
+ outConfig->keyRemapping = mLocked.keyRemapping;
} // release lock
}
@@ -1910,6 +1916,16 @@
mInputManager->getDispatcher().setInputMethodConnectionIsActive(isActive);
}
+void NativeInputManager::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+ mLocked.keyRemapping = keyRemapping;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::KEY_REMAPPING);
+}
+
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -1983,10 +1999,19 @@
return vec;
}
-static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
- jint fromKeyCode, jint toKeyCode) {
+static void nativeSetKeyRemapping(JNIEnv* env, jobject nativeImplObj, jintArray fromKeyCodesArr,
+ jintArray toKeyCodesArr) {
+ const std::vector<int32_t> fromKeycodes = getIntArray(env, fromKeyCodesArr);
+ const std::vector<int32_t> toKeycodes = getIntArray(env, toKeyCodesArr);
+ if (fromKeycodes.size() != toKeycodes.size()) {
+ jniThrowRuntimeException(env, "FromKeycodes and toKeycodes cannot match.");
+ }
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+ std::map<int32_t, int32_t> keyRemapping;
+ for (int i = 0; i < fromKeycodes.size(); i++) {
+ keyRemapping.insert_or_assign(fromKeycodes[i], toKeycodes[i]);
+ }
+ im->setKeyRemapping(keyRemapping);
}
static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
@@ -2491,7 +2516,7 @@
jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightTypeKeyboardMicMute);
} else {
- ALOGW("Unknown light type %d", lightInfo.type);
+ ALOGW("Unknown light type %s", ftl::enum_string(lightInfo.type).c_str());
continue;
}
@@ -2955,7 +2980,7 @@
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
- {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
+ {"setKeyRemapping", "([I[I)V", (void*)nativeSetKeyRemapping},
{"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
{"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
{"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c381..bc64e15 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@
import com.android.internal.infra.AndroidFuture
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,7 +45,6 @@
class MetadataSyncAdapterTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
- private val testExecutor = MoreExecutors.directExecutor()
private val packageManager = context.packageManager
@Test
@@ -138,8 +136,7 @@
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 3dd2f24a..1cad255 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -509,8 +509,12 @@
mAppStartInfoTracker.handleProcessBroadcastStart(3, app, buildIntent(COMPONENT),
false /* isAlarm */);
+ // Add a brief delay between timestamps to make sure the clock, which is in milliseconds has
+ // actually incremented.
+ sleep(1);
mAppStartInfoTracker.handleProcessBroadcastStart(2, app, buildIntent(COMPONENT),
false /* isAlarm */);
+ sleep(1);
mAppStartInfoTracker.handleProcessBroadcastStart(1, app, buildIntent(COMPONENT),
false /* isAlarm */);
@@ -557,9 +561,10 @@
// Now load from disk.
mAppStartInfoTracker.loadExistingProcessStartInfo();
- // Confirm clock has been set and that its current time is greater than the previous one.
+ // Confirm clock has been set and that its current time is greater than or equal to the
+ // previous one, thereby ensuring it was loaded from disk.
assertNotNull(mAppStartInfoTracker.mMonotonicClock);
- assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() > originalMonotonicTime);
+ assertTrue(mAppStartInfoTracker.mMonotonicClock.monotonicTime() >= originalMonotonicTime);
}
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index f6ad07d..2107406 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -229,8 +229,10 @@
doCallRealMethod().when(mService).enqueueOomAdjTargetLocked(any(ProcessRecord.class));
doCallRealMethod().when(mService).updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_ACTIVITY);
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
- doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
- .enqueueProcessChangeItemLocked(anyInt(), anyInt());
+ doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+ anyInt());
+ doNothing().when(pr).enqueueProcessChangeItemLocked(anyInt(), anyInt(), anyInt(),
+ anyBoolean());
mService.mOomAdjuster = mService.mConstants.ENABLE_NEW_OOMADJ
? new OomAdjusterModernImpl(mService, mService.mProcessList,
new ActiveUids(mService, false), mInjector)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 7ff2e50..4b03483 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -29,6 +29,8 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -75,6 +77,7 @@
import android.app.ActivityOptions;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -97,9 +100,13 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
@@ -122,6 +129,9 @@
@RunWith(WindowTestRunner.class)
public class TaskTests extends WindowTestsBase {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final String TASK_TAG = "task";
private Rect mParentBounds;
@@ -404,6 +414,85 @@
}
@Test
+ @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+ public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setComponent(
+ ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .build();
+ task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+ // Override should take effect and task should be resizeable.
+ assertTrue(task.getTaskInfo().isResizeable);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
+ public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setComponent(
+ ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .build();
+ task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+
+ // Disallow resize overrides.
+ task.mAllowForceResizeOverride = false;
+
+ // Override should not take effect and task should be un-resizeable.
+ assertFalse(task.getTaskInfo().isResizeable);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+ public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setComponent(
+ ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .build();
+ task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+ // Override should take effect and task should be un-resizeable.
+ assertFalse(task.getTaskInfo().isResizeable);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+ public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setComponent(
+ ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .build();
+ task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+ // Disallow resize overrides.
+ task.mAllowForceResizeOverride = false;
+
+ // Override should not take effect and task should be resizeable.
+ assertTrue(task.getTaskInfo().isResizeable);
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
+ public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setCreateActivity(true)
+ .setComponent(
+ ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .build();
+ task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+
+ // Set system-wide force resizeable override.
+ task.mAtmService.mForceResizableActivities = true;
+
+ // System wide override should tak priority over app compat override so the task should
+ // remain resizeable.
+ assertTrue(task.getTaskInfo().isResizeable);
+ }
+
+ @Test
public void testResolveNonResizableTaskWindowingMode() {
// Test with no support non-resizable in multi window regardless the screen size.
mAtm.mSupportsNonResizableMultiWindow = -1;
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS
new file mode 100644
index 0000000..981b316
--- /dev/null
+++ b/tests/FlickerTests/ActivityEmbedding/OWNERS
@@ -0,0 +1 @@
+# Bug component: 1168918
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index b3a998e..9459070 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -321,26 +321,26 @@
new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED);
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169));
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
// Color should not change because hardware state of a different touchpad
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE);
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
}
@Test