Merge "Fix up ModifierShortcutsTests to use test bookmarks.xml" into main
diff --git a/Android.bp b/Android.bp
index 9933940..f0aa62c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -427,7 +427,6 @@
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
"libaconfig_java_proto_nano",
- "aconfig_device_paths_java",
],
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7bea0489..36b1eab 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -42,7 +42,6 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -6854,9 +6853,6 @@
}
private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
- if (!activityWindowInfoFlag()) {
- return;
- }
if (r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
return;
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 0c1e7a3..c281533 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -19,8 +19,6 @@
import static android.app.WindowConfiguration.areConfigurationsEqualForDisplay;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
-
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
@@ -102,9 +100,6 @@
*/
public void registerActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
- if (!activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
mActivityWindowInfoChangedListeners.add(listener);
}
@@ -116,9 +111,6 @@
*/
public void unregisterActivityWindowInfoChangedListener(
@NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
- if (!activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
mActivityWindowInfoChangedListeners.remove(listener);
}
@@ -130,9 +122,6 @@
*/
public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
@NonNull ActivityWindowInfo activityWindowInfo) {
- if (!activityWindowInfoFlag()) {
- return;
- }
final Object[] activityWindowInfoChangedListeners;
synchronized (mLock) {
if (mActivityWindowInfoChangedListeners.isEmpty()) {
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4894fb1..0321e1df 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -395,7 +395,7 @@
* the display is removed.
*
* Public virtual displays without this flag will move their content to main display
- * stack once they're removed. Private vistual displays will always destroy their
+ * stack once they're removed. Private virtual displays will always destroy their
* content on removal even without this flag.
*
* @see #createVirtualDisplay
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a6c6c18..e5b17c8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -128,7 +128,6 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme;
-import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay;
import static com.android.window.flags.Flags.enableCaptionCompatInsetForceConsumption;
import static com.android.window.flags.Flags.insetsControlChangedItem;
@@ -1374,9 +1373,6 @@
*/
public void setActivityConfigCallback(@Nullable ActivityConfigCallback callback) {
mActivityConfigCallback = callback;
- if (!activityWindowInfoFlag()) {
- return;
- }
if (callback == null) {
mPendingActivityWindowInfo = null;
mLastReportedActivityWindowInfo = null;
@@ -9360,7 +9356,7 @@
onClientWindowFramesChanged(mTmpFrames);
- if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
+ if (mPendingActivityWindowInfo != null) {
final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo;
if (outInfo != null) {
mPendingActivityWindowInfo.set(outInfo);
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index d4c3fbe..15f1258 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -328,6 +328,7 @@
* only occupies a portion of Task bounds.
* @hide
*/
+ // TODO(b/287582673): cleanup
public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
try {
return getController().isActivityEmbedded(activityToken);
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 8fd525c..5397e91 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -208,7 +208,7 @@
flag {
name: "enforce_shell_thread_model"
- namespace: "windowing_frentend"
+ namespace: "windowing_frontend"
description: "Crash the shell process if someone calls in from the wrong thread"
bug: "351189446"
is_fixed_read_only: true
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 2b096ea..3e6f18e 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -188,9 +188,17 @@
*/
public static final int CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU = 112;
+ /** Track Launcher Keyboard Quick Switch View opening animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN = 113;
+
+ /** Track Launcher Keyboard Quick Switch View closing animation */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE = 114;
+
+ /** Track launching an app through the Launcher Keyboard Quick Switch View */
+ public static final int CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH = 115;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
/** @hide */
@IntDef({
@@ -294,7 +302,10 @@
CUJ_DESKTOP_MODE_EXIT_MODE,
CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
CUJ_DESKTOP_MODE_DRAG_WINDOW,
- CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP
+ CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE,
+ CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -409,6 +420,9 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH;
}
private Cuj() {
@@ -629,6 +643,12 @@
return "DESKTOP_MODE_DRAG_WINDOW";
case CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP:
return "STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE";
+ case CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH:
+ return "LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 30fa4f1..2ad6651 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -12,7 +12,7 @@
flag {
name: "use_transaction_codes_for_unknown_methods"
- namespace: "dropbox"
+ namespace: "stability"
description: "Use transaction codes when the method names is unknown"
bug: "350041302"
is_fixed_read_only: true
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 086fcc8..f306b0b 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -18,7 +18,6 @@
import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
-import android.aconfig.DevicePaths;
import android.aconfig.nano.Aconfig;
import android.aconfig.nano.Aconfig.parsed_flag;
import android.aconfig.nano.Aconfig.parsed_flags;
@@ -41,6 +40,7 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.List;
import java.util.Map;
/**
@@ -54,20 +54,20 @@
public class AconfigFlags {
private static final String LOG_TAG = "AconfigFlags";
- public enum Permission {
- READ_WRITE,
- READ_ONLY
- }
+ private static final List<String> sTextProtoFilesOnDevice = List.of(
+ "/system/etc/aconfig_flags.pb",
+ "/system_ext/etc/aconfig_flags.pb",
+ "/product/etc/aconfig_flags.pb",
+ "/vendor/etc/aconfig_flags.pb");
private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
- private final ArrayMap<String, Permission> mFlagPermissions = new ArrayMap<>();
public AconfigFlags() {
if (!Flags.manifestFlagging()) {
Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
return;
}
- for (String fileName : DevicePaths.parsedFlagsProtoPaths()) {
+ for (String fileName : sTextProtoFilesOnDevice) {
try (var inputStream = new FileInputStream(fileName)) {
loadAconfigDefaultValues(inputStream.readAllBytes());
} catch (IOException e) {
@@ -184,12 +184,6 @@
Slog.v(LOG_TAG, "Read Aconfig default flag value "
+ flagPackageAndName + " = " + flagValue);
mFlagValues.put(flagPackageAndName, flagValue);
-
- Permission permission = flag.permission == Aconfig.READ_ONLY
- ? Permission.READ_ONLY
- : Permission.READ_WRITE;
-
- mFlagPermissions.put(flagPackageAndName, permission);
}
}
@@ -206,17 +200,6 @@
}
/**
- * Get the flag permission, or null if the flag doesn't exist.
- * @param flagPackageAndName Full flag name formatted as 'package.flag'
- * @return the current permission of the given Aconfig flag, or null if there is no such flag
- */
- @Nullable
- public Permission getFlagPermission(@NonNull String flagPackageAndName) {
- Permission permission = mFlagPermissions.get(flagPackageAndName);
- return permission;
- }
-
- /**
* Check if the element in {@code parser} should be skipped because of the feature flag.
* @param parser XML parser object currently parsing an element
* @return true if the element is disabled because of its feature flag
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 50fb8d5..652cba7 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -98,6 +98,7 @@
this::onTracingFlush,
this::onTracingInstanceStop
);
+ @Nullable
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
@@ -126,7 +127,7 @@
}
public PerfettoProtoLogImpl(
- ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
@@ -136,8 +137,8 @@
@VisibleForTesting
public PerfettoProtoLogImpl(
- ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- ProtoLogViewerConfigReader viewerConfigReader,
+ @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @Nullable ProtoLogViewerConfigReader viewerConfigReader,
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
@@ -209,7 +210,9 @@
* @return status code
*/
public int startLoggingToLogcat(String[] groups, ILogger logger) {
- mViewerConfigReader.loadViewerConfig(logger);
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.loadViewerConfig(groups, logger);
+ }
return setTextLogging(true, logger, groups);
}
@@ -220,7 +223,9 @@
* @return status code
*/
public int stopLoggingToLogcat(String[] groups, ILogger logger) {
- mViewerConfigReader.unloadViewerConfig();
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.unloadViewerConfig(groups, logger);
+ }
return setTextLogging(false, logger, groups);
}
@@ -262,7 +267,9 @@
return -1;
}
case "enable-text" -> {
- mViewerConfigReader.loadViewerConfig(logger);
+ if (mViewerConfigReader != null) {
+ mViewerConfigReader.loadViewerConfig(groups, logger);
+ }
return setTextLogging(true, logger, groups);
}
case "disable-text" -> {
@@ -420,7 +427,12 @@
private void logToLogcat(String tag, LogLevel level, Message message,
@Nullable Object[] args) {
- String messageString = message.getMessage(mViewerConfigReader);
+ String messageString;
+ if (mViewerConfigReader == null) {
+ messageString = message.getMessage();
+ } else {
+ messageString = message.getMessage(mViewerConfigReader);
+ }
if (messageString == null) {
StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
@@ -827,7 +839,11 @@
return mMessageMask;
}
- private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) {
+ private String getMessage() {
+ return mMessageString;
+ }
+
+ private String getMessage(@NonNull ProtoLogViewerConfigReader viewerConfigReader) {
if (mMessageString != null) {
return mMessageString;
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index b7b2424..bb6c8b7 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -1,20 +1,32 @@
package com.android.internal.protolog;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME;
+
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE;
import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID;
-import android.util.ArrayMap;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.LongSparseArray;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
import java.io.IOException;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
public class ProtoLogViewerConfigReader {
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private Map<Long, String> mLogMessageMap = null;
+ private final Map<String, Set<Long>> mGroupHashes = new TreeMap<>();
+ private final LongSparseArray<String> mLogMessageMap = new LongSparseArray<>();
public ProtoLogViewerConfigReader(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
@@ -26,39 +38,62 @@
* or the viewer config is not loaded into memory.
*/
public synchronized String getViewerString(long messageHash) {
- if (mLogMessageMap != null) {
- return mLogMessageMap.get(messageHash);
- } else {
- return null;
- }
+ return mLogMessageMap.get(messageHash);
+ }
+
+ public synchronized void loadViewerConfig(String[] groups) {
+ loadViewerConfig(groups, (message) -> {});
}
/**
* Loads the viewer config into memory. No-op if already loaded in memory.
*/
- public synchronized void loadViewerConfig(ILogger logger) {
- if (mLogMessageMap != null) {
- return;
- }
+ public synchronized void loadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (mGroupHashes.containsKey(group)) {
+ continue;
+ }
- try {
- doLoadViewerConfig();
- logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
- } catch (IOException e) {
- logger.log("Unable to load log definitions: "
- + "IOException while processing viewer config" + e);
+ try {
+ Map<Long, String> mappings = loadViewerConfigMappingForGroup(group);
+ mGroupHashes.put(group, mappings.keySet());
+ for (Long key : mappings.keySet()) {
+ mLogMessageMap.put(key, mappings.get(key));
+ }
+
+ logger.log("Loaded " + mLogMessageMap.size() + " log definitions");
+ } catch (IOException e) {
+ logger.log("Unable to load log definitions: "
+ + "IOException while processing viewer config" + e);
+ }
}
}
+ public synchronized void unloadViewerConfig(String[] groups) {
+ unloadViewerConfig(groups, (message) -> {});
+ }
+
/**
* Unload the viewer config from memory.
*/
- public synchronized void unloadViewerConfig() {
- mLogMessageMap = null;
+ public synchronized void unloadViewerConfig(String[] groups, @NonNull ILogger logger) {
+ for (String group : groups) {
+ if (!mGroupHashes.containsKey(group)) {
+ continue;
+ }
+
+ final Set<Long> hashes = mGroupHashes.get(group);
+ for (Long hash : hashes) {
+ logger.log("Unloading viewer config hash " + hash);
+ mLogMessageMap.remove(hash);
+ }
+ }
}
- private void doLoadViewerConfig() throws IOException {
- mLogMessageMap = new ArrayMap<>();
+ private Map<Long, String> loadViewerConfigMappingForGroup(String group) throws IOException {
+ Long targetGroupId = loadGroupId(group);
+
+ final Map<Long, String> hashesForGroup = new TreeMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
@@ -67,6 +102,7 @@
long messageId = 0;
String message = null;
+ int groupId = 0;
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
switch (pis.getFieldNumber()) {
case (int) MESSAGE_ID:
@@ -75,9 +111,16 @@
case (int) MESSAGE:
message = pis.readString(MESSAGE);
break;
+ case (int) GROUP_ID:
+ groupId = pis.readInt(GROUP_ID);
+ break;
}
}
+ if (groupId == 0) {
+ throw new IOException("Failed to get group id");
+ }
+
if (messageId == 0) {
throw new IOException("Failed to get message id");
}
@@ -86,10 +129,45 @@
throw new IOException("Failed to get message string");
}
- mLogMessageMap.put(messageId, message);
+ if (groupId == targetGroupId) {
+ hashesForGroup.put(messageId, message);
+ }
pis.end(inMessageToken);
}
}
+
+ return hashesForGroup;
+ }
+
+ private Long loadGroupId(String group) throws IOException {
+ final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
+
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (pis.getFieldNumber() == (int) GROUPS) {
+ final long inMessageToken = pis.start(GROUPS);
+
+ long groupId = 0;
+ String groupName = null;
+ while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (pis.getFieldNumber()) {
+ case (int) ID:
+ groupId = pis.readInt(ID);
+ break;
+ case (int) NAME:
+ groupName = pis.readString(NAME);
+ break;
+ }
+ }
+
+ if (Objects.equals(groupName, group)) {
+ return groupId;
+ }
+
+ pis.end(inMessageToken);
+ }
+ }
+
+ throw new RuntimeException("Group " + group + "not found in viewer config");
}
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index f87a9e2..e8a0762 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -25,8 +25,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
-
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -810,7 +808,6 @@
@Test
public void testActivityWindowInfoChanged_activityLaunch() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
@@ -825,7 +822,6 @@
@Test
public void testActivityWindowInfoChanged_activityRelaunch() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
@@ -866,7 +862,6 @@
@Test
public void testActivityWindowInfoChanged_activityConfigurationChanged() {
- mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
mActivityWindowInfoListener);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 0b270d4..d2a444f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -53,8 +53,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.window.flags.Flags;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -167,8 +165,6 @@
@Test
public void testActivityWindowInfoChangedListener() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 3261a37..b120723 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -204,11 +204,9 @@
/** Listener registered to {@link ClientTransactionListenerController}. */
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
- Flags.activityWindowInfoFlag()
- ? this::onActivityWindowInfoChanged
- : null;
+ this::onActivityWindowInfoChanged;
private final Handler mHandler;
private final MainThreadExecutor mExecutor;
@@ -3096,20 +3094,14 @@
public boolean isActivityEmbedded(@NonNull Activity activity) {
Objects.requireNonNull(activity);
synchronized (mLock) {
- if (Flags.activityWindowInfoFlag()) {
- final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
- return activityWindowInfo != null && activityWindowInfo.isEmbedded();
- }
- return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null && activityWindowInfo.isEmbedded();
}
}
@Override
public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
@NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
synchronized (mLock) {
@@ -3123,9 +3115,6 @@
@Override
public void clearEmbeddedActivityWindowInfoCallback() {
- if (!Flags.activityWindowInfoFlag()) {
- return;
- }
synchronized (mLock) {
if (mEmbeddedActivityWindowInfoCallback == null) {
return;
@@ -3146,9 +3135,6 @@
@Nullable
@Override
public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
- if (!Flags.activityWindowInfoFlag()) {
- return null;
- }
synchronized (mLock) {
final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
return activityWindowInfo != null
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index efeec82..99c0ee2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -103,8 +103,6 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
-import com.android.window.flags.Flags;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -1557,8 +1555,6 @@
@Test
public void testIsActivityEmbedded() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
assertFalse(mSplitController.isActivityEmbedded(mActivity));
doReturn(true).when(mActivityWindowInfo).isEmbedded();
@@ -1568,8 +1564,6 @@
@Test
public void testGetEmbeddedActivityWindowInfo() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final boolean isEmbedded = true;
final Rect taskBounds = new Rect(0, 0, 1000, 2000);
final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
@@ -1584,8 +1578,6 @@
@Test
public void testSetEmbeddedActivityWindowInfoCallback() {
- mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
final ClientTransactionListenerController controller = ClientTransactionListenerController
.getInstance();
spyOn(controller);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index dbcad8a..a00d003 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -224,7 +224,6 @@
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
"com_android_wm_shell_flags_lib",
- "com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
"WindowManager-Shell-shared",
"perfetto_trace_java_protos",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 6315e69..5807246 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1107,6 +1107,10 @@
if (useDesktopOverrideDensity()) {
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
}
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/**
@@ -1122,6 +1126,10 @@
// The task's density may have been overridden in freeform; revert it here as we don't
// want it overridden in multi-window.
wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+ if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskInfo.taskId)) {
+ // Remove wallpaper activity when leaving desktop mode
+ removeWallpaperActivity(wct)
+ }
}
/** Returns the ID of the Task that will be minimized, or null if no task will be minimized. */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index f670434..8558a77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -935,6 +935,24 @@
}
@Test
+ fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -946,6 +964,44 @@
}
@Test
+ fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
+ val task = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+
+ controller.moveToFullscreen(task.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ // Removes wallpaper activity when leaving desktop
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveToFullscreen_multipleVisibleNonMinimizedTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ // Setup task2
+ setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
+ .configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+
+ controller.moveToFullscreen(task1.taskId, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val task1Change = assertNotNull(wct.changes[task1.token.asBinder()])
+ assertThat(task1Change.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ // Does not remove wallpaper activity, as desktop still has a visible desktop task
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
fun moveToFullscreen_nonExistentTask_doesNothing() {
controller.moveToFullscreen(999, transitionSource = UNKNOWN)
verifyExitDesktopWCTNotExecuted()
@@ -1769,6 +1825,49 @@
}
@Test
+ fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ wct.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun moveFocusedTaskToFullscreen_multipleVisibleTasks_doesNotRemoveWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ controller.enterFullscreen(DEFAULT_DISPLAY, transitionSource = UNKNOWN)
+
+ val wct = getLatestExitDesktopWct()
+ val taskChange = assertNotNull(wct.changes[task2.token.asBinder()])
+ assertThat(taskChange.windowingMode).isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wct.hierarchyOps).isEmpty()
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
@@ -1977,6 +2076,7 @@
eq(null))
}
+ @Test
fun enterSplit_freeformTaskIsMovedToSplit() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -1986,14 +2086,67 @@
task2.isFocused = true
task3.isFocused = false
- controller.enterSplit(DEFAULT_DISPLAY, false)
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
verify(splitScreenController)
.requestEnterSplitSelect(
- task2,
+ eq(task2),
any(),
- SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
- task2.configuration.windowConfiguration.bounds)
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ }
+
+ @Test
+ fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+ desktopModeTaskRepository.minimizeTask(DEFAULT_DISPLAY, task1.taskId)
+ desktopModeTaskRepository.updateVisibleFreeformTasks(DEFAULT_DISPLAY, task3.taskId,
+ visible = false)
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Removes wallpaper activity when leaving desktop
+ wctArgument.value.assertRemoveAt(index = 0, wallpaperToken)
+ }
+
+ @Test
+ fun enterSplit_multipleVisibleNonMinimizedTasks_removesWallpaperActivity() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+ val wallpaperToken = MockToken().token()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+ desktopModeTaskRepository.wallpaperActivityToken = wallpaperToken
+
+ controller.enterSplit(DEFAULT_DISPLAY, leftOrTop = false)
+
+ val wctArgument = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ verify(splitScreenController)
+ .requestEnterSplitSelect(
+ eq(task2),
+ wctArgument.capture(),
+ eq(SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT),
+ eq(task2.configuration.windowConfiguration.bounds))
+ // Does not remove wallpaper activity, as desktop still has visible desktop tasks
+ assertThat(wctArgument.value.hierarchyOps).isEmpty()
}
@Test
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 073bc8d..b4e6b72 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -28,6 +28,7 @@
extern int register_android_graphics_Bitmap(JNIEnv*);
extern int register_android_graphics_BitmapFactory(JNIEnv*);
+extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
extern int register_android_graphics_Camera(JNIEnv* env);
extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
@@ -53,8 +54,11 @@
extern int register_android_graphics_DrawFilter(JNIEnv* env);
extern int register_android_graphics_FontFamily(JNIEnv* env);
extern int register_android_graphics_Gainmap(JNIEnv* env);
+extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
extern int register_android_graphics_HardwareRendererObserver(JNIEnv* env);
extern int register_android_graphics_Matrix(JNIEnv* env);
+extern int register_android_graphics_Mesh(JNIEnv* env);
+extern int register_android_graphics_MeshSpecification(JNIEnv* env);
extern int register_android_graphics_Paint(JNIEnv* env);
extern int register_android_graphics_Path(JNIEnv* env);
extern int register_android_graphics_PathIterator(JNIEnv* env);
@@ -87,6 +91,8 @@
static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
{"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
+ {"android.graphics.BitmapRegionDecoder",
+ REG_JNI(register_android_graphics_BitmapRegionDecoder)},
{"android.graphics.ByteBufferStreamAdaptor",
REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
{"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)},
@@ -101,6 +107,8 @@
{"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
{"android.graphics.Gainmap", REG_JNI(register_android_graphics_Gainmap)},
{"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+ {"android.graphics.HardwareBufferRenderer",
+ REG_JNI(register_android_graphics_HardwareBufferRenderer)},
{"android.graphics.HardwareRenderer", REG_JNI(register_android_view_ThreadedRenderer)},
{"android.graphics.HardwareRendererObserver",
REG_JNI(register_android_graphics_HardwareRendererObserver)},
@@ -108,6 +116,9 @@
{"android.graphics.Interpolator", REG_JNI(register_android_graphics_Interpolator)},
{"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
{"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
+ {"android.graphics.Mesh", REG_JNI(register_android_graphics_Mesh)},
+ {"android.graphics.MeshSpecification",
+ REG_JNI(register_android_graphics_MeshSpecification)},
{"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
{"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
{"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index ae9792d..b943496 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -126,7 +126,7 @@
SkSafeUnref(meshSpec);
}
-static jlong getMeshSpecificationFinalizer() {
+static jlong getMeshSpecificationFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshSpecification_safeUnref));
}
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index e3cdee6..3b1b861 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -135,7 +135,7 @@
proxy->setLightAlpha((uint8_t)(255 * ambientShadowAlpha), (uint8_t)(255 * spotShadowAlpha));
}
-static jlong android_graphics_HardwareBufferRenderer_getFinalizer() {
+static jlong android_graphics_HardwareBufferRenderer_getFinalizer(CRITICAL_JNI_PARAMS) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&HardwareBufferRenderer_destroy));
}
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index bbf0315..4387b6f 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -16,6 +16,8 @@
package com.android.settingslib.widget;
+import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -122,6 +124,8 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
+ R.id.illustration_frame);
final ImageView backgroundView =
(ImageView) holder.findViewById(R.id.background_view);
final FrameLayout middleGroundLayout =
@@ -130,15 +134,15 @@
(LottieAnimationView) holder.findViewById(R.id.lottie_view);
if (illustrationView != null && !TextUtils.isEmpty(mContentDescription)) {
illustrationView.setContentDescription(mContentDescription);
- illustrationView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ illustrationView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ final View illustrationContainer = (View) illustrationFrame.getParent();
+ illustrationContainer.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// To solve the problem of non-compliant illustrations, we set the frame height
// to 300dp and set the length of the short side of the screen to
// the width of the frame.
final int screenWidth = getContext().getResources().getDisplayMetrics().widthPixels;
final int screenHeight = getContext().getResources().getDisplayMetrics().heightPixels;
- final FrameLayout illustrationFrame = (FrameLayout) holder.findViewById(
- R.id.illustration_frame);
final LayoutParams lp = (LayoutParams) illustrationFrame.getLayoutParams();
lp.width = screenWidth < screenHeight ? screenWidth : screenHeight;
illustrationFrame.setLayoutParams(lp);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 8b0772b..2227943 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -42,7 +42,6 @@
import android.provider.UpdatableDeviceConfigServiceReadiness;
import android.util.Slog;
-import com.android.internal.pm.pkg.component.AconfigFlags;
import com.android.internal.util.FastPrintWriter;
import java.io.File;
@@ -417,13 +416,7 @@
DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case OVERRIDE:
- AconfigFlags.Permission permission =
- (new AconfigFlags()).getFlagPermission(key);
- if (permission == AconfigFlags.Permission.READ_ONLY) {
- pout.println("cannot override read-only flag " + key);
- } else {
- DeviceConfig.setLocalOverride(namespace, key, value);
- }
+ DeviceConfig.setLocalOverride(namespace, key, value);
break;
case CLEAR_OVERRIDE:
DeviceConfig.clearLocalOverride(namespace, key);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index db71d72..1871873 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -581,7 +581,6 @@
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
"androidx.lifecycle_lifecycle-viewmodel-compose",
- "device_policy_aconfig_flags_lib",
],
libs: [
"keepanno-annotations",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 6e6e4b2..71f5511 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1171,3 +1171,19 @@
bug: "345227709"
}
+flag {
+ namespace: "systemui"
+ name: "register_content_observers_async"
+ description: "Use new Async API to register content observers"
+ bug: "316922634"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "msdl_feedback"
+ namespace: "systemui"
+ description: "Enables MSDL feedback in SysUI surfaces."
+ bug: "352600066"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index f6535ec0..8f247f6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -94,6 +94,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -171,7 +172,11 @@
var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
- val gridState = rememberLazyGridState()
+
+ val gridState =
+ rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset)
+ viewModel.clearPersistedScrollPosition()
+
val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle()
val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle()
@@ -187,6 +192,8 @@
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
+ ObserveScrollEffect(gridState, viewModel)
+
if (!viewModel.isEditMode) {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
@@ -420,6 +427,20 @@
}
}
+@Composable
+private fun ObserveScrollEffect(
+ gridState: LazyGridState,
+ communalViewModel: BaseCommunalViewModel
+) {
+
+ LaunchedEffect(gridState) {
+ snapshotFlow {
+ Pair(gridState.firstVisibleItemIndex, gridState.firstVisibleItemScrollOffset)
+ }
+ .collect { communalViewModel.onScrollPositionUpdated(it.first, it.second) }
+ }
+}
+
/**
* Observes communal content and scrolls to any added or updated live content, e.g. a new media
* session is started, or a paused timer is resumed.
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 525839d..b4c839f 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -9168,39 +9168,6 @@
<issue
id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleKeyguardDone");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2654"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleShow");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="2780"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
- message="The `beginSection()` call is not always closed with a matching `endSection()` because the code in between may return early"
- errorLine1=" Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java"
- line="3011"
- column="15"/>
- </issue>
-
- <issue
- id="UnclosedTrace"
message="The `traceBegin()` call is not always closed with a matching `traceEnd()` because the code in between may return early"
errorLine1=" Trace.traceBegin(Trace.TRACE_TAG_APP, "MediaControlPanel#bindPlayer<" + key + ">");"
errorLine2=" ~~~~~~~~~~">
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
new file mode 100644
index 0000000..ad2c42f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.systemui.communal.data.db
+
+import android.content.ComponentName
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
+import com.android.systemui.communal.widgets.CommunalWidgetHost
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class DefaultWidgetPopulationTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val communalWidgetHost =
+ mock<CommunalWidgetHost> {
+ var nextId = 0
+ on { allocateIdAndBindWidget(any(), anyOrNull()) }.thenAnswer { nextId++ }
+ }
+ private val communalWidgetDao = mock<CommunalWidgetDao>()
+ private val database = mock<SupportSQLiteDatabase>()
+ private val mainUser = UserHandle(0)
+ private val userManager =
+ mock<UserManager> {
+ on { mainUser }.thenReturn(mainUser)
+ on { getUserSerialNumber(0) }.thenReturn(0)
+ }
+
+ private val defaultWidgets =
+ arrayOf(
+ "com.android.test_package_1/fake_widget_1",
+ "com.android.test_package_2/fake_widget_2",
+ "com.android.test_package_3/fake_widget_3",
+ )
+
+ private lateinit var underTest: DefaultWidgetPopulation
+
+ @Before
+ fun setUp() {
+ underTest =
+ DefaultWidgetPopulation(
+ bgScope = kosmos.applicationCoroutineScope,
+ communalWidgetHost = communalWidgetHost,
+ communalWidgetDaoProvider = { communalWidgetDao },
+ defaultWidgets = defaultWidgets,
+ logBuffer = logcatLogBuffer("DefaultWidgetPopulationTest"),
+ userManager = userManager,
+ )
+ }
+
+ @Test
+ fun testPopulateDefaultWidgetsWhenDatabaseCreated() =
+ testScope.runTest {
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify default widgets bound
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[0])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[1])!!),
+ user = eq(mainUser),
+ )
+ verify(communalWidgetHost)
+ .allocateIdAndBindWidget(
+ provider = eq(ComponentName.unflattenFromString(defaultWidgets[2])!!),
+ user = eq(mainUser),
+ )
+
+ // Verify default widgets added in database
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 0,
+ componentName = defaultWidgets[0],
+ priority = 3,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 1,
+ componentName = defaultWidgets[1],
+ priority = 2,
+ userSerialNumber = 0,
+ )
+ verify(communalWidgetDao)
+ .addWidget(
+ widgetId = 2,
+ componentName = defaultWidgets[2],
+ priority = 1,
+ userSerialNumber = 0,
+ )
+ }
+
+ @Test
+ fun testSkipDefaultWidgetsPopulation() =
+ testScope.runTest {
+ // Skip default widgets population
+ underTest.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
+ // Database created
+ underTest.onCreate(database)
+ runCurrent()
+
+ // Verify no widget bounded or added to the database
+ verify(communalWidgetHost, never()).allocateIdAndBindWidget(any(), any())
+ verify(communalWidgetDao, never())
+ .addWidget(
+ widgetId = anyInt(),
+ componentName = any(),
+ priority = anyInt(),
+ userSerialNumber = anyInt(),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 17234a90..c707ebf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.defaultWidgetPopulation
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toByteArray
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -134,6 +135,7 @@
backupUtils,
packageChangeRepository,
userManager,
+ kosmos.defaultWidgetPopulation,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index a2f6796..b138fb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -40,6 +40,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
@@ -76,6 +77,8 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -94,6 +97,7 @@
private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
private lateinit var communalSceneInteractor: CommunalSceneInteractor
+ private lateinit var communalInteractor: CommunalInteractor
private val testableResources = context.orCreateTestableResources
@@ -108,6 +112,7 @@
smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository
mediaRepository = kosmos.fakeCommunalMediaRepository
communalSceneInteractor = kosmos.communalSceneInteractor
+ communalInteractor = spy(kosmos.communalInteractor)
kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
kosmos.fakeUserTracker.set(
userInfos = listOf(MAIN_USER_INFO),
@@ -119,7 +124,7 @@
underTest =
CommunalEditModeViewModel(
communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.keyguardTransitionInteractor,
mediaHost,
@@ -346,6 +351,16 @@
assertThat(showDisclaimer).isFalse()
}
+ @Test
+ fun scrollPosition_persistedOnEditCleanup() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.cleanupEditModeState()
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 74a048d..c480aa8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -37,6 +37,7 @@
import com.android.systemui.communal.data.repository.fakeCommunalSmartspaceRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
@@ -97,7 +98,9 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -121,6 +124,7 @@
private lateinit var shadeTestUtil: ShadeTestUtil
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var communalRepository: FakeCommunalSceneRepository
+ private lateinit var communalInteractor: CommunalInteractor
private lateinit var underTest: CommunalViewModel
@@ -154,6 +158,8 @@
kosmos.powerInteractor.setAwakeForTest()
+ communalInteractor = spy(kosmos.communalInteractor)
+
underTest =
CommunalViewModel(
kosmos.testDispatcher,
@@ -164,7 +170,7 @@
kosmos.keyguardInteractor,
mock<KeyguardIndicationController>(),
kosmos.communalSceneInteractor,
- kosmos.communalInteractor,
+ communalInteractor,
kosmos.communalSettingsInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
@@ -779,6 +785,16 @@
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
+ @Test
+ fun scrollPosition_persistedOnEditEntry() {
+ val index = 2
+ val offset = 30
+ underTest.onScrollPositionUpdated(index, offset)
+ underTest.onOpenWidgetEditor(false)
+
+ verify(communalInteractor).setScrollPosition(eq(index), eq(offset))
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index d7b7cfe..583c10f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -20,7 +20,6 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
@@ -42,7 +41,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ModesTileDataInteractorTest : SysuiTestCase() {
private val zenModeRepository = FakeZenModeRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index b5e47d1..fd1b213 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -85,7 +85,6 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -182,7 +181,6 @@
kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
buildNotificationRows(isPinned = false)
- advanceTimeBy(50L) // account for HeadsUpNotificationInteractor debounce
assertThat(isVisible).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index 5ef3485..8b4265f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,7 +23,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -280,64 +279,6 @@
}
@Test
- fun isHeadsUpOrAnimatingAway_falseOnStart() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isFalse()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_hasPinnedRows() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAway() =
- testScope.runTest {
- val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)
-
- // WHEN the last row is animating away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- assertThat(isHeadsUpOrAnimatingAway).isTrue()
- }
-
- @Test
- fun isHeadsUpOrAnimatingAway_headsUpAnimatingAwayDebounced() =
- testScope.runTest {
- val values by collectValues(underTest.isHeadsUpOrAnimatingAway)
-
- // GIVEN a row is pinned
- headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
- runCurrent()
- assertThat(values.size).isEqualTo(2)
- assertThat(values.first()).isFalse() // initial value
- assertThat(values.last()).isTrue()
-
- // WHEN the last row is removed
- headsUpRepository.setNotifications(emptyList())
- runCurrent()
- // AND starts to animate away
- headsUpRepository.setHeadsUpAnimatingAway(true)
- runCurrent()
-
- // THEN isHeadsUpOrAnimatingAway remained true
- assertThat(values.size).isEqualTo(2)
- assertThat(values.last()).isTrue()
- }
-
- @Test
fun showHeadsUpStatusBar_true() =
testScope.runTest {
val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 4dcd9bf..933a25a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -27,7 +27,7 @@
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.widgets.CommunalWidgetHost
import com.android.systemui.communal.widgets.CommunalWidgetModule.Companion.DEFAULT_WIDGETS
-import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -35,21 +35,19 @@
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/**
* Callback that will be invoked when the Room database is created. Then the database will be
* populated with pre-configured default widgets to be rendered in the glanceable hub.
*/
+@SysUISingleton
class DefaultWidgetPopulation
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val bgScope: CoroutineScope,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDaoProvider: Provider<CommunalWidgetDao>,
@Named(DEFAULT_WIDGETS) private val defaultWidgets: Array<String>,
@@ -62,36 +60,43 @@
private val logger = Logger(logBuffer, TAG)
+ /**
+ * Reason for skipping default widgets population. Do not skip if this value is
+ * [SkipReason.NONE].
+ */
+ private var skipReason = SkipReason.NONE
+
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
- applicationScope.launch { addDefaultWidgets() }
- }
- // Read default widgets from config.xml and populate the database.
- private suspend fun addDefaultWidgets() =
- withContext(bgDispatcher) {
+ if (skipReason != SkipReason.NONE) {
+ logger.i("Skipped populating default widgets. Reason: $skipReason")
+ return
+ }
+
+ bgScope.launch {
// Default widgets should be associated with the main user.
- val userSerialNumber =
- userManager.mainUser?.let { mainUser ->
- userManager.getUserSerialNumber(mainUser.identifier)
- }
- if (userSerialNumber == null) {
+ val user = userManager.mainUser
+
+ if (user == null) {
logger.w(
- "Skipped populating default widgets because device does not have a main user"
+ "Skipped populating default widgets. Reason: device does not have a main user"
)
- return@withContext
+ return@launch
}
+ val userSerialNumber = userManager.getUserSerialNumber(user.identifier)
+
defaultWidgets.forEachIndexed { index, name ->
val provider = ComponentName.unflattenFromString(name)
provider?.let {
- val id = communalWidgetHost.allocateIdAndBindWidget(provider)
+ val id = communalWidgetHost.allocateIdAndBindWidget(provider, user)
id?.let {
communalWidgetDaoProvider
.get()
.addWidget(
widgetId = id,
- provider = provider,
+ componentName = name,
priority = defaultWidgets.size - index,
userSerialNumber = userSerialNumber,
)
@@ -101,6 +106,25 @@
logger.i("Populated default widgets in the database.")
}
+ }
+
+ /**
+ * Skip populating default widgets in the Glanceable Hub when the database is created. This has
+ * no effect if default widgets have been populated already.
+ *
+ * @param skipReason Reason for skipping the default widgets population.
+ */
+ fun skipDefaultWidgetsPopulation(skipReason: SkipReason) {
+ this.skipReason = skipReason
+ }
+
+ /** Reason for skipping default widgets population. */
+ enum class SkipReason {
+ /** Do not skip. */
+ NONE,
+ /** Widgets are restored from a backup. */
+ RESTORED_FROM_BACKUP,
+ }
}
@Dao
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index ab4c9d2..e65e5e5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -26,6 +26,8 @@
import com.android.systemui.communal.data.backup.CommunalBackupUtils
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation
+import com.android.systemui.communal.data.db.DefaultWidgetPopulation.SkipReason.RESTORED_FROM_BACKUP
import com.android.systemui.communal.nano.CommunalHubState
import com.android.systemui.communal.proto.toCommunalHubState
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
@@ -101,6 +103,7 @@
private val backupUtils: CommunalBackupUtils,
packageChangeRepository: PackageChangeRepository,
private val userManager: UserManager,
+ private val defaultWidgetPopulation: DefaultWidgetPopulation,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -321,6 +324,9 @@
}
val newState = CommunalHubState().apply { widgets = newWidgets.toTypedArray() }
+ // Skip default widgets population
+ defaultWidgetPopulation.skipDefaultWidgetsPopulation(RESTORED_FROM_BACKUP)
+
// Restore database
logger.i("Restoring communal database:\n$newState")
communalWidgetDao.restoreCommunalHubState(newState)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 597a2ce..3fffd76 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -541,4 +541,29 @@
)
}
}
+
+ /**
+ * {@link #setScrollPosition} persists the current communal grid scroll position (to volatile
+ * memory) so that the next presentation of the grid (either as glanceable hub or edit mode) can
+ * restore position.
+ */
+ fun setScrollPosition(firstVisibleItemIndex: Int, firstVisibleItemOffset: Int) {
+ _firstVisibleItemIndex = firstVisibleItemIndex
+ _firstVisibleItemOffset = firstVisibleItemOffset
+ }
+
+ fun resetScrollPosition() {
+ _firstVisibleItemIndex = 0
+ _firstVisibleItemOffset = 0
+ }
+
+ val firstVisibleItemIndex: Int
+ get() = _firstVisibleItemIndex
+
+ private var _firstVisibleItemIndex: Int = 0
+
+ val firstVisibleItemOffset: Int
+ get() = _firstVisibleItemOffset
+
+ private var _firstVisibleItemOffset: Int = 0
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 6ec6ec1..19d7ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -59,6 +59,18 @@
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
+ /**
+ * The up-to-date value of the grid scroll offset. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollOffset = 0
+
+ /**
+ * The up-to-date value of the grid scroll index. persisted to interactor on
+ * {@link #persistScrollPosition}
+ */
+ private var currentScrollIndex = 0
+
fun signalUserInteraction() {
communalInteractor.signalUserInteraction()
}
@@ -147,6 +159,28 @@
/** Called as the user request to show the customize widget button. */
open fun onLongClick() {}
+ /** Called when the grid scroll position has been updated. */
+ open fun onScrollPositionUpdated(firstVisibleItemIndex: Int, firstVisibleItemScroll: Int) {
+ currentScrollIndex = firstVisibleItemIndex
+ currentScrollOffset = firstVisibleItemScroll
+ }
+
+ /** Stores scroll values to interactor. */
+ protected fun persistScrollPosition() {
+ communalInteractor.setScrollPosition(currentScrollIndex, currentScrollOffset)
+ }
+
+ /** Invoked after scroll values are used to initialize grid position. */
+ open fun clearPersistedScrollPosition() {
+ communalInteractor.setScrollPosition(0, 0)
+ }
+
+ val savedFirstScrollIndex: Int
+ get() = communalInteractor.firstVisibleItemIndex
+
+ val savedFirstScrollOffset: Int
+ get() = communalInteractor.firstVisibleItemOffset
+
/** Set the key of the currently selected item */
fun setSelectedKey(key: String?) {
_selectedKey.value = key
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 91f4c1c..7b0aadf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -220,6 +220,9 @@
/** Called when exiting the edit mode, before transitioning back to the communal scene. */
fun cleanupEditModeState() {
communalSceneInteractor.setEditModeState(null)
+
+ // Set the scroll position of the glanceable hub to match where we are now.
+ persistScrollPosition()
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 780bf70..02ecfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -233,7 +233,10 @@
override fun onOpenWidgetEditor(
shouldOpenWidgetPickerOnStart: Boolean,
- ) = communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ ) {
+ persistScrollPosition()
+ communalInteractor.showWidgetEditor(selectedKey.value, shouldOpenWidgetPickerOnStart)
+ }
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 9e53792..49817b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -20,6 +20,7 @@
import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.util.Log
+import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
@@ -47,8 +48,11 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -56,6 +60,7 @@
@Inject
constructor(
private val context: Context,
+ @Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@SystemShortcuts private val systemShortcutsSource: KeyboardShortcutGroupsSource,
@MultitaskingShortcuts private val multitaskingShortcutsSource: KeyboardShortcutGroupsSource,
@@ -75,81 +80,82 @@
}
}
- val systemShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- System,
- systemShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
- )
- } else {
- null
- }
- }
-
- val multitaskingShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- MultiTasking,
- multitaskingShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
- )
- } else {
- null
- }
- }
-
- val appCategoriesShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- AppCategories,
- appCategoriesShortcutsSource.shortcutGroups(it.id),
- keepIcons = true,
- )
- } else {
- null
- }
- }
-
- val imeShortcutsCategory =
- activeInputDevice.map {
- if (it != null) {
- toShortcutCategory(
- it.keyCharacterMap,
- InputMethodEditor,
- inputShortcutsSource.shortcutGroups(it.id),
- keepIcons = false,
- )
- } else {
- null
- }
- }
-
- val currentAppShortcutsCategory: Flow<ShortcutCategory?> =
- activeInputDevice.map {
- if (it != null) {
- val shortcutGroups = currentAppShortcutsSource.shortcutGroups(it.id)
- val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
- if (categoryType == null) {
- null
- } else {
- toShortcutCategory(
- it.keyCharacterMap,
- categoryType,
- shortcutGroups,
- keepIcons = false
- )
+ val categories: Flow<List<ShortcutCategory>> =
+ activeInputDevice
+ .map {
+ if (it == null) {
+ return@map emptyList()
}
- } else {
- null
+ return@map listOfNotNull(
+ fetchSystemShortcuts(it),
+ fetchMultiTaskingShortcuts(it),
+ fetchAppCategoriesShortcuts(it),
+ fetchImeShortcuts(it),
+ fetchCurrentAppShortcuts(it),
+ )
}
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.Lazily,
+ initialValue = emptyList(),
+ )
+
+ private suspend fun fetchSystemShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ System,
+ systemShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
+
+ private suspend fun fetchMultiTaskingShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ MultiTasking,
+ multitaskingShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
+
+ private suspend fun fetchAppCategoriesShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ AppCategories,
+ appCategoriesShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = true,
+ )
+
+ private suspend fun fetchImeShortcuts(inputDevice: InputDevice) =
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ InputMethodEditor,
+ inputShortcutsSource.shortcutGroups(inputDevice.id),
+ keepIcons = false,
+ )
+
+ private suspend fun fetchCurrentAppShortcuts(inputDevice: InputDevice): ShortcutCategory? {
+ val shortcutGroups = currentAppShortcutsSource.shortcutGroups(inputDevice.id)
+ val categoryType = getCurrentAppShortcutCategoryType(shortcutGroups)
+ return if (categoryType == null) {
+ null
+ } else {
+ toShortcutCategory(
+ inputDevice.keyCharacterMap,
+ categoryType,
+ shortcutGroups,
+ keepIcons = false
+ )
}
+ }
+
+ private fun getCurrentAppShortcutCategoryType(
+ shortcutGroups: List<KeyboardShortcutGroup>
+ ): ShortcutCategoryType? {
+ return if (shortcutGroups.isEmpty()) {
+ null
+ } else {
+ CurrentApp(packageName = shortcutGroups[0].packageName.toString())
+ }
+ }
private fun toShortcutCategory(
keyCharacterMap: KeyCharacterMap,
@@ -174,16 +180,6 @@
}
}
- private fun getCurrentAppShortcutCategoryType(
- shortcutGroups: List<KeyboardShortcutGroup>
- ): ShortcutCategoryType? {
- return if (shortcutGroups.isEmpty()) {
- null
- } else {
- CurrentApp(packageName = shortcutGroups[0].packageName.toString())
- }
- }
-
private fun toShortcuts(
keyCharacterMap: KeyCharacterMap,
infoList: List<KeyboardShortcutInfo>,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index f215c74..6f19561 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -23,7 +23,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@@ -33,14 +33,8 @@
) {
val shortcutCategories: Flow<List<ShortcutCategory>> =
- combine(
- categoriesRepository.systemShortcutsCategory,
- categoriesRepository.multitaskingShortcutsCategory,
- categoriesRepository.imeShortcutsCategory,
- categoriesRepository.appCategoriesShortcutsCategory,
- categoriesRepository.currentAppShortcutsCategory
- ) { shortcutCategories ->
- shortcutCategories.filterNotNull().map { groupSubCategoriesInCategory(it) }
+ categoriesRepository.categories.map { categories ->
+ categories.map { category -> groupSubCategoriesInCategory(category) }
}
private fun groupSubCategoriesInCategory(shortcutCategory: ShortcutCategory): ShortcutCategory {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index e602cad..ad258f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -19,7 +19,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -40,8 +39,8 @@
) {
val shouldShow =
- stateInteractor.state
- .map { it is ShortcutHelperState.Active }
+ categoriesInteractor.shortcutCategories
+ .map { it.isNotEmpty() }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe81b20c..2f872b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2834,6 +2834,14 @@
*/
private void handleShow(Bundle options) {
Trace.beginSection("KeyguardViewMediator#handleShow");
+ try {
+ handleShowInner(options);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleShowInner(Bundle options) {
final boolean showUnlocked = options != null
&& options.getBoolean(OPTION_SHOW_DISMISSIBLE, false);
final int currentUser = mSelectedUserInteractor.getSelectedUserId();
@@ -2885,8 +2893,6 @@
mKeyguardDisplayManager.show();
scheduleNonStrongBiometricIdleTimeout();
-
- Trace.endSection();
}
/**
@@ -3065,6 +3071,17 @@
RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("KeyguardViewMediator#handleStartKeyguardExitAnimation");
+ try {
+ handleStartKeyguardExitAnimationInner(startTime, fadeoutDuration, apps, wallpapers,
+ nonApps, finishedCallback);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void handleStartKeyguardExitAnimationInner(long startTime, long fadeoutDuration,
+ RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
synchronized (KeyguardViewMediator.this) {
@@ -3253,8 +3270,6 @@
onKeyguardExitFinished();
}
}
-
- Trace.endSection();
}
private void onKeyguardExitFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index ab432d6..c0049d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.util.kotlin.combine
@@ -104,21 +103,21 @@
val clockShouldBeCentered: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
combine(
- shadeInteractor.shadeMode,
+ shadeInteractor.isShadeLayoutWide,
activeNotificationsInteractor.areAnyNotificationsPresent,
keyguardInteractor.isActiveDreamLockscreenHosted,
isOnAod,
headsUpNotificationInteractor.isHeadsUpOrAnimatingAway,
keyguardInteractor.isDozing,
) {
- shadeMode,
+ isShadeLayoutWide,
areAnyNotificationsPresent,
isActiveDreamLockscreenHosted,
isOnAod,
isHeadsUp,
isDozing ->
when {
- shadeMode != ShadeMode.Split -> true
+ !isShadeLayoutWide -> true
!areAnyNotificationsPresent -> true
isActiveDreamLockscreenHosted -> true
// Pulsing notification appears on the right. Move clock left to avoid overlap.
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
index 071d8f8c..760ff7d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepository.kt
@@ -36,14 +36,16 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
class MediaProjectionManagerRepository
@Inject
constructor(
@@ -76,12 +78,12 @@
object : MediaProjectionManager.Callback() {
override fun onStart(info: MediaProjectionInfo?) {
Log.d(TAG, "MediaProjectionManager.Callback#onStart")
- trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ trySendWithFailureLogging(CallbackEvent.OnStart, TAG)
}
override fun onStop(info: MediaProjectionInfo?) {
Log.d(TAG, "MediaProjectionManager.Callback#onStop")
- trySendWithFailureLogging(MediaProjectionState.NotProjecting, TAG)
+ trySendWithFailureLogging(CallbackEvent.OnStop, TAG)
}
override fun onRecordingSessionSet(
@@ -89,14 +91,36 @@
session: ContentRecordingSession?
) {
Log.d(TAG, "MediaProjectionManager.Callback#onSessionStarted: $session")
- launch {
- trySendWithFailureLogging(stateForSession(info, session), TAG)
- }
+ trySendWithFailureLogging(
+ CallbackEvent.OnRecordingSessionSet(info, session),
+ TAG,
+ )
}
}
mediaProjectionManager.addCallback(callback, handler)
awaitClose { mediaProjectionManager.removeCallback(callback) }
}
+ // When we get an #onRecordingSessionSet event, we need to do some work in the
+ // background before emitting the right state value. But when we get an #onStop
+ // event, we immediately know what state value to emit.
+ //
+ // Without `mapLatest`, this could be a problem if an #onRecordingSessionSet event
+ // comes in and then an #onStop event comes in shortly afterwards (b/352483752):
+ // 1. #onRecordingSessionSet -> start some work in the background
+ // 2. #onStop -> immediately emit "Not Projecting"
+ // 3. onRecordingSessionSet work finishes -> emit "Projecting"
+ //
+ // At step 3, we *shouldn't* emit "Projecting" because #onStop was the last callback
+ // event we received, so we should be "Not Projecting". This `mapLatest` ensures
+ // that if an #onStop event comes in, we cancel any ongoing work for
+ // #onRecordingSessionSet and we don't emit "Projecting".
+ .mapLatest {
+ when (it) {
+ is CallbackEvent.OnStart,
+ is CallbackEvent.OnStop -> MediaProjectionState.NotProjecting
+ is CallbackEvent.OnRecordingSessionSet -> stateForSession(it.info, it.session)
+ }
+ }
.stateIn(
scope = applicationScope,
started = SharingStarted.Lazily,
@@ -129,6 +153,21 @@
return MediaProjectionState.Projecting.SingleTask(hostPackage, hostDeviceName, matchingTask)
}
+ /**
+ * Translates [MediaProjectionManager.Callback] events into objects so that we always maintain
+ * the correct callback ordering.
+ */
+ sealed interface CallbackEvent {
+ data object OnStart : CallbackEvent
+
+ data object OnStop : CallbackEvent
+
+ data class OnRecordingSessionSet(
+ val info: MediaProjectionInfo,
+ val session: ContentRecordingSession?,
+ ) : CallbackEvent
+ }
+
companion object {
private const val TAG = "MediaProjectionMngrRepo"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index c971f54..edc49cac2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -183,9 +183,9 @@
Context context,
InternetDialogManager internetDialogManager,
InternetDialogController internetDialogController,
- @Assisted(ABOVE_STATUS_BAR) boolean canConfigMobileData,
- @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigWifi,
- @Assisted(CAN_CONFIG_WIFI) boolean aboveStatusBar,
+ @Assisted(CAN_CONFIG_MOBILE_DATA) boolean canConfigMobileData,
+ @Assisted(CAN_CONFIG_WIFI) boolean canConfigWifi,
+ @Assisted(ABOVE_STATUS_BAR) boolean aboveStatusBar,
@Assisted CoroutineScope coroutineScope,
UiEventLogger uiEventLogger,
DialogTransitionAnimator dialogTransitionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index c1f8a0b..45f359e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -55,7 +55,11 @@
/** Whether the shade can be expanded from QQS to QS. */
val isExpandToQsEnabled: Flow<Boolean>
- /** The version of the shade layout to use. */
+ /**
+ * The version of the shade layout to use.
+ *
+ * Note: Most likely, you want to read [isShadeLayoutWide] instead of this.
+ */
val shadeMode: StateFlow<ShadeMode>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
index 8214a24..a8199a4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt
@@ -29,6 +29,8 @@
/**
* The split shade where, on large screens and unfolded foldables, the QS and notification parts
* are placed side-by-side and expand/collapse as a single panel.
+ *
+ * Note: This isn't the only mode where the shade is wide.
*/
data object Split : ShadeMode
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index eebbb13..bf44b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.notification.domain.interactor
@@ -25,17 +25,14 @@
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
class HeadsUpNotificationInteractor
@Inject
@@ -50,48 +47,54 @@
val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
/** Set of currently pinned top-level heads up rows to be displayed. */
- val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
- if (repositories.isNotEmpty()) {
- val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
- repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } }
- combine(toCombine) { pairs ->
- pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(emptySet())
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
+ if (repositories.isNotEmpty()) {
+ val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
+ repositories.map { repo ->
+ repo.isPinned.map { isPinned -> repo to isPinned }
+ }
+ combine(toCombine) { pairs ->
+ pairs.filter { (_, isPinned) -> isPinned }.map { (repo, _) -> repo }.toSet()
+ }
+ } else {
+ // if the set is empty, there are no flows to combine
+ flowOf(emptySet())
}
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(emptySet())
}
}
+ }
/** Are there any pinned heads up rows to display? */
- val hasPinnedRows: Flow<Boolean> =
- headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
- if (rows.isNotEmpty()) {
- combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
- } else {
- // if the set is empty, there are no flows to combine
- flowOf(false)
- }
- }
-
- val isHeadsUpOrAnimatingAway: Flow<Boolean> =
- combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
- hasPinnedRows,
- animatingAway ->
- hasPinnedRows || animatingAway
- }
- .debounce { isHeadsUpOrAnimatingAway ->
- if (isHeadsUpOrAnimatingAway) {
- 0
+ val hasPinnedRows: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
+ if (rows.isNotEmpty()) {
+ combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
} else {
- // When the last pinned entry is removed from the [HeadsUpRepository],
- // there might be a delay before the View starts animating.
- 50L
+ // if the set is empty, there are no flows to combine
+ flowOf(false)
}
}
- .onStart { emit(false) } // emit false, so we don't wait for the initial update
- .distinctUntilChanged()
+ }
+ }
+
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
+ hasPinnedRows,
+ animatingAway ->
+ hasPinnedRows || animatingAway
+ }
+ }
+ }
private val canShowHeadsUp: Flow<Boolean> =
combine(
@@ -109,10 +112,15 @@
}
}
- val showHeadsUpStatusBar: Flow<Boolean> =
- combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
- hasPinnedRows && canShowHeadsUp
+ val showHeadsUpStatusBar: Flow<Boolean> by lazy {
+ if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
+ flowOf(false)
+ } else {
+ combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+ hasPinnedRows && canShowHeadsUp
+ }
}
+ }
fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
HeadsUpRowInteractor(key as HeadsUpRowRepository)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a11cbc3..98869be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -37,6 +37,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,6 +55,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Optional;
@@ -86,6 +88,7 @@
private final FoldAodAnimationController mFoldAodAnimationController;
private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private final UserTracker mUserTracker;
+ private final SecureSettings mSecureSettings;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
@@ -130,7 +133,8 @@
ConfigurationController configurationController,
StatusBarStateController statusBarStateController,
UserTracker userTracker,
- DozeInteractor dozeInteractor) {
+ DozeInteractor dozeInteractor,
+ SecureSettings secureSettings) {
mResources = resources;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -144,6 +148,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mUserTracker = userTracker;
mDozeInteractor = dozeInteractor;
+ mSecureSettings = secureSettings;
keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
tunerService.addTunable(
@@ -160,7 +165,8 @@
mFoldAodAnimationController.addCallback(this);
}
- SettingsObserver quickPickupSettingsObserver = new SettingsObserver(context, handler);
+ SettingsObserver quickPickupSettingsObserver =
+ new SettingsObserver(context, handler, mSecureSettings);
quickPickupSettingsObserver.observe();
batteryController.addCallback(new BatteryStateChangeCallback() {
@@ -479,18 +485,36 @@
Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON);
private final Context mContext;
- SettingsObserver(Context context, Handler handler) {
+ private final Handler mHandler;
+ private final SecureSettings mSecureSettings;
+
+ SettingsObserver(Context context, Handler handler, SecureSettings secureSettings) {
super(handler);
mContext = context;
+ mHandler = handler;
+ mSecureSettings = secureSettings;
}
void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(mQuickPickupGesture, false, this,
- UserHandle.USER_ALL);
- resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
- resolver.registerContentObserver(mAlwaysOnEnabled, false, this, UserHandle.USER_ALL);
- update(null);
+ if (Flags.registerContentObserversAsync()) {
+ mSecureSettings.registerContentObserverForUserAsync(mQuickPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mPickupGesture,
+ this, UserHandle.USER_ALL);
+ mSecureSettings.registerContentObserverForUserAsync(mAlwaysOnEnabled,
+ this, UserHandle.USER_ALL,
+ // The register calls are called in order, so this ensures that update()
+ // is called after them all and value retrieval isn't racy.
+ () -> mHandler.post(() -> update(null)));
+ } else {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(mQuickPickupGesture, false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(mPickupGesture, false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(mAlwaysOnEnabled, false, this,
+ UserHandle.USER_ALL);
+ update(null);
+ }
}
@Override
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 3bf5b65..025354b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -21,6 +21,7 @@
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings.SettingNotFoundException
+import androidx.annotation.WorkerThread
import com.android.app.tracing.TraceUtils.trace
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
@@ -199,6 +200,24 @@
}
/**
+ * Convenience wrapper around [ContentResolver.registerContentObserver].'
+ *
+ * API corresponding to [registerContentObserverForUser] for Java usage. After registration is
+ * complete, the callback block is called on the <b>background thread</b> to allow for update of
+ * value.
+ */
+ fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int,
+ @WorkerThread registered: Runnable
+ ) =
+ CoroutineScope(backgroundDispatcher).launch {
+ registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+ registered.run()
+ }
+
+ /**
* Convenience wrapper around [ContentResolver.registerContentObserver]
*
* Implicitly calls [getUriFor] on the passed in name.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
new file mode 100644
index 0000000..14837f2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.systemui.keyboard.shortcut.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
+
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+
+ private val kosmos =
+ testKosmos().also {
+ it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ }
+
+ private val repo = kosmos.shortcutHelperCategoriesRepository
+ private val helper = kosmos.shortcutHelperTestHelper
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
+ @Test
+ fun categories_multipleSubscribers_replaysExistingValueToNewSubscribers() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ helper.showFromActivity()
+ val firstCategories by collectLastValue(repo.categories)
+
+ // Intentionally change shortcuts now. This simulates "current app" shortcuts changing
+ // when our helper is shown.
+ // We still want to return the shortcuts that were returned before our helper was
+ // showing.
+ fakeSystemSource.setGroups(emptyList())
+
+ val secondCategories by collectLastValue(repo.categories)
+ // Make sure the second subscriber receives the same value as the first subscriber, even
+ // though fetching shortcuts again would have returned a new result.
+ assertThat(secondCategories).isEqualTo(firstCategories)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index d20ce3f..57c8b44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
@@ -48,14 +49,14 @@
private val systemShortcutsSource = FakeKeyboardShortcutGroupsSource()
private val multitaskingShortcutsSource = FakeKeyboardShortcutGroupsSource()
- private val defaultAppsShortcutsSource = FakeKeyboardShortcutGroupsSource()
@OptIn(ExperimentalCoroutinesApi::class)
private val kosmos =
testKosmos().also {
it.testDispatcher = UnconfinedTestDispatcher()
it.shortcutHelperSystemShortcutsSource = systemShortcutsSource
it.shortcutHelperMultiTaskingShortcutsSource = multitaskingShortcutsSource
- it.shortcutHelperAppCategoriesShortcutsSource = defaultAppsShortcutsSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
index 0757ea1..f8e2f47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
@@ -20,8 +20,15 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity
import com.android.systemui.keyboard.shortcut.shortcutHelperActivityStarter
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity
import com.android.systemui.kosmos.Kosmos
@@ -32,6 +39,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,10 +48,18 @@
@RunWith(AndroidJUnit4::class)
class ShortcutHelperActivityStarterTest : SysuiTestCase() {
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+
private val kosmos =
Kosmos().also {
it.testCase = this
it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val testScope = kosmos.testScope
@@ -51,6 +67,12 @@
private val fakeStartActivity = kosmos.fakeShortcutHelperStartActivity
private val starter = kosmos.shortcutHelperActivityStarter
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
@Test
fun start_doesNotStartByDefault() =
testScope.runTest {
@@ -70,13 +92,30 @@
}
@Test
+ fun start_onToggle_noShortcuts_doesNotStartActivity() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(emptyList())
+ fakeMultiTaskingSource.setGroups(emptyList())
+
+ starter.start()
+
+ testHelper.toggle(deviceId = 456)
+
+ assertThat(fakeStartActivity.startIntents).isEmpty()
+ }
+
+ @Test
fun start_onToggle_multipleTimesStartsActivityOnlyWhenNotStarted() =
testScope.runTest {
starter.start()
+ // Starts
testHelper.toggle(deviceId = 456)
+ // Stops
testHelper.toggle(deviceId = 456)
+ // Starts again
testHelper.toggle(deviceId = 456)
+ // Stops
testHelper.toggle(deviceId = 456)
verifyShortcutHelperActivityStarted(numTimes = 2)
@@ -93,6 +132,18 @@
}
@Test
+ fun start_onRequestShowShortcuts_noShortcuts_doesNotStartActivity() =
+ testScope.runTest {
+ fakeSystemSource.setGroups(emptyList())
+ fakeMultiTaskingSource.setGroups(emptyList())
+ starter.start()
+
+ testHelper.showFromActivity()
+
+ assertThat(fakeStartActivity.startIntents).isEmpty()
+ }
+
+ @Test
fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyOnce() =
testScope.runTest {
starter.start()
@@ -109,13 +160,21 @@
testScope.runTest {
starter.start()
+ // No-op. Already hidden.
testHelper.hideFromActivity()
+ // No-op. Already hidden.
testHelper.hideForSystem()
+ // Show 1st time.
testHelper.toggle(deviceId = 987)
+ // No-op. Already shown.
testHelper.showFromActivity()
+ // Hidden.
testHelper.hideFromActivity()
+ // No-op. Already hidden.
testHelper.hideForSystem()
+ // Show 2nd time.
testHelper.toggle(deviceId = 456)
+ // No-op. Already shown.
testHelper.showFromActivity()
verifyShortcutHelperActivityStarted(numTimes = 2)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 80d487c..07feaa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -21,6 +21,13 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
+import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
@@ -34,6 +41,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,10 +50,18 @@
@RunWith(AndroidJUnit4::class)
class ShortcutHelperViewModelTest : SysuiTestCase() {
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+
private val kosmos =
Kosmos().also {
it.testCase = this
it.testDispatcher = UnconfinedTestDispatcher()
+ it.shortcutHelperSystemShortcutsSource = fakeSystemSource
+ it.shortcutHelperMultiTaskingShortcutsSource = fakeMultiTaskingSource
+ it.shortcutHelperAppCategoriesShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperInputShortcutsSource = FakeKeyboardShortcutGroupsSource()
+ it.shortcutHelperCurrentAppShortcutsSource = FakeKeyboardShortcutGroupsSource()
}
private val testScope = kosmos.testScope
@@ -53,6 +69,12 @@
private val sysUiState = kosmos.sysUiState
private val viewModel = kosmos.shortcutHelperViewModel
+ @Before
+ fun setUp() {
+ fakeSystemSource.setGroups(TestShortcuts.systemGroups)
+ fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ }
+
@Test
fun shouldShow_falseByDefault() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index c604b6a..5db8981 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -19,6 +19,7 @@
import android.hardware.display.displayManager
import android.media.projection.MediaProjectionInfo
import android.os.Binder
+import android.os.Handler
import android.os.UserHandle
import android.view.ContentRecordingSession
import android.view.Display
@@ -26,11 +27,14 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeTasksRepository
import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
@@ -253,6 +257,50 @@
.isNull()
}
+ /** Regression test for b/352483752. */
+ @Test
+ fun mediaProjectionState_sessionStartedThenImmediatelyStopped_emitsOnlyNotProjecting() =
+ testScope.runTest {
+ val fakeTasksRepo = FakeTasksRepository()
+ val repoWithTimingControl =
+ MediaProjectionManagerRepository(
+ // fakeTasksRepo lets us have control over when the background dispatcher
+ // finishes fetching the tasks info.
+ tasksRepository = fakeTasksRepo,
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ displayManager = displayManager,
+ handler = Handler.getMain(),
+ applicationScope = kosmos.applicationCoroutineScope,
+ backgroundDispatcher = kosmos.testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ )
+
+ val state by collectLastValue(repoWithTimingControl.mediaProjectionState)
+
+ val token = createToken()
+ val task = createTask(taskId = 1, token = token)
+
+ // Dispatch a session using a task session so that MediaProjectionManagerRepository
+ // has to ask TasksRepository for the tasks info.
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = ContentRecordingSession.createTaskSession(token.asBinder())
+ )
+ // FakeTasksRepository is set up to not return the tasks info until the test manually
+ // calls [FakeTasksRepository#setRunningTaskResult]. At this point,
+ // MediaProjectionManagerRepository is waiting for the tasks info and hasn't emitted
+ // anything yet.
+
+ // Before the tasks info comes back, dispatch a stop event.
+ fakeMediaProjectionManager.dispatchOnStop()
+
+ // Then let the tasks info come back.
+ fakeTasksRepo.setRunningTaskResult(task)
+
+ // Verify that MediaProjectionManagerRepository threw away the tasks info because
+ // a newer callback event (#onStop) occurred.
+ assertThat(state).isEqualTo(MediaProjectionState.NotProjecting)
+ }
+
@Test
fun stopProjecting_invokesManager() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
new file mode 100644
index 0000000..ce2b983
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeTasksRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.mediaprojection.taskswitcher.data.repository
+
+import android.app.ActivityManager
+import android.os.IBinder
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Fake tasks repository that gives us fine-grained control over when the result of
+ * [findRunningTaskFromWindowContainerToken] gets emitted.
+ */
+class FakeTasksRepository : TasksRepository {
+ override suspend fun launchRecentTask(taskInfo: ActivityManager.RunningTaskInfo) {}
+
+ private val findRunningTaskResult: CompletableDeferred<ActivityManager.RunningTaskInfo?> =
+ CompletableDeferred()
+
+ override suspend fun findRunningTaskFromWindowContainerToken(
+ windowContainerToken: IBinder
+ ): ActivityManager.RunningTaskInfo? {
+ return findRunningTaskResult.await()
+ }
+
+ fun setRunningTaskResult(task: ActivityManager.RunningTaskInfo?) {
+ findRunningTaskResult.complete(task)
+ }
+
+ override val foregroundTask: Flow<ActivityManager.RunningTaskInfo> = emptyFlow()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 7cb41f1..10d07a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -35,8 +35,8 @@
import android.os.PowerManager;
import android.provider.Settings;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
@@ -52,6 +52,7 @@
import com.android.systemui.tuner.TunerService;
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.util.settings.FakeSettings;
import org.junit.Assert;
import org.junit.Before;
@@ -130,7 +131,8 @@
mConfigurationController,
mStatusBarStateController,
mUserTracker,
- mDozeInteractor
+ mDozeInteractor,
+ new FakeSettings()
);
verify(mBatteryController).addCallback(mBatteryStateChangeCallback.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ef4e734..cc2ef53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.ui.viewmodel
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,6 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -126,6 +128,7 @@
}
@Test
+ @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
fun isVisible_headsUpStatusBarShown_false() =
testScope.runTest {
val latest by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index e3e20c8..5f7420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -31,9 +31,11 @@
import com.android.systemui.settings.UserTracker
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Before
@@ -65,20 +67,21 @@
}
@Test
- fun registerContentObserverForUser_inputString_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_success() =
@@ -98,13 +101,14 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -113,24 +117,24 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputString_notifyForDescendants_true() =
@@ -153,14 +157,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -169,23 +174,23 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserverForUser_inputUri_success() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_success() =
@@ -205,13 +210,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_success() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
+
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -220,24 +227,41 @@
eq(MAIN_USER_ID)
)
}
- }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun registerContentObserverForUserAsync_callbackAfterRegister() =
+ testScope.runTest {
+ var callbackCalled = false
+ val runnable = { callbackCalled = true }
+
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ mContentObserver,
+ mUserTracker.userId,
+ runnable
+ )
+ testScope.advanceUntilIdle()
+ assertThat(callbackCalled).isTrue()
+ }
@Test
- fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(true),
- eq(mContentObserver),
- eq(MAIN_USER_ID)
+ fun registerContentObserverForUser_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
)
- }
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(MAIN_USER_ID)
+ )
+ }
@Test
fun registerContentObserverForUserSuspend_inputUri_notifyForDescendants_true() =
@@ -260,14 +284,15 @@
}
@Test
- fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverForUserAsync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver,
- mUserTracker.userId
- )
- testScope.launch {
+ fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverForUserAsync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver,
+ mUserTracker.userId
+ )
+ testScope.advanceUntilIdle()
verify(mSettings.getContentResolver())
.registerContentObserver(
eq(TEST_SETTING_URI),
@@ -276,14 +301,19 @@
eq(MAIN_USER_ID)
)
}
- }
@Test
- fun registerContentObserver_inputUri_success() {
- mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_success() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_success() =
@@ -313,15 +343,21 @@
}
@Test
- fun registerContentObserver_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverSync(
- TEST_SETTING_URI,
- notifyForDescendants = true,
- mContentObserver
- )
- verify(mSettings.getContentResolver())
- .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0))
- }
+ fun registerContentObserver_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverSync(
+ TEST_SETTING_URI,
+ notifyForDescendants = true,
+ mContentObserver
+ )
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(true),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
@Test
fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
@@ -337,18 +373,19 @@
}
@Test
- fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
- mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
- testScope.launch {
- verify(mSettings.getContentResolver())
- .registerContentObserver(
- eq(TEST_SETTING_URI),
- eq(false),
- eq(mContentObserver),
- eq(0)
- )
+ fun registerContentObserverAsync_inputUri_notifyForDescendants_true() =
+ testScope.runTest {
+ mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+ testScope.launch {
+ verify(mSettings.getContentResolver())
+ .registerContentObserver(
+ eq(TEST_SETTING_URI),
+ eq(false),
+ eq(mContentObserver),
+ eq(0)
+ )
+ }
}
- }
@Test
fun getString_keyPresent_returnValidValue() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
new file mode 100644
index 0000000..8d01fcd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/db/DefaultWidgetPopulationKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.systemui.communal.data.db
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.defaultWidgetPopulation by
+ Kosmos.Fixture<DefaultWidgetPopulation> { mock(DefaultWidgetPopulation::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 530df8a..001b55b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -68,16 +68,17 @@
)
}
-val Kosmos.shortcutHelperInputShortcutsSource by
+var Kosmos.shortcutHelperInputShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { InputShortcutsSource(mainResources, windowManager) }
-val Kosmos.shortcutHelperCurrentAppShortcutsSource by
+var Kosmos.shortcutHelperCurrentAppShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture { CurrentAppShortcutsSource(windowManager) }
val Kosmos.shortcutHelperCategoriesRepository by
Kosmos.Fixture {
ShortcutHelperCategoriesRepository(
applicationContext,
+ applicationCoroutineScope,
testDispatcher,
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
@@ -96,7 +97,8 @@
applicationContext,
broadcastDispatcher,
fakeCommandQueue,
- windowManager
+ fakeInputManager,
+ windowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 40510db..3e09b23 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.Intent
+import android.hardware.input.FakeInputManager
import android.view.KeyboardShortcutGroup
import android.view.WindowManager
import android.view.WindowManager.KeyboardShortcutsReceiver
@@ -31,6 +32,7 @@
private val context: Context,
private val fakeBroadcastDispatcher: FakeBroadcastDispatcher,
private val fakeCommandQueue: FakeCommandQueue,
+ private val fakeInputManager: FakeInputManager,
windowManager: WindowManager
) {
@@ -79,6 +81,7 @@
}
fun toggle(deviceId: Int) {
+ fakeInputManager.addPhysicalKeyboard(deviceId)
fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1cd20ed..9d4310c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -215,6 +215,7 @@
"power_hint_flags_lib",
"biometrics_flags_lib",
"am_flags_lib",
+ "updates_flags_lib",
"com_android_server_accessibility_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index d214788..5315167 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -163,7 +163,7 @@
flag {
name: "collect_logcat_on_run_synchronously"
- namespace: "dropbox"
+ namespace: "stability"
description: "Allow logcat collection on synchronous dropbox collection"
bug: "324222683"
is_fixed_read_only: true
@@ -171,7 +171,7 @@
flag {
name: "enable_dropbox_watchdog_headers"
- namespace: "dropbox"
+ namespace: "stability"
description: "Add watchdog-specific dropbox headers"
bug: "330682397"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8ee02dc4..303371b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2629,18 +2629,28 @@
String packageName = pkgLite.packageName;
synchronized (mPm.mLock) {
- // Package which currently owns the data that the new package will own if installed.
- // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
- // will be null whereas dataOwnerPkg will contain information about the package
- // which was uninstalled while keeping its data.
- AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
- if (dataOwnerPkg == null) {
- if (dataOwnerPs != null) {
- dataOwnerPkg = dataOwnerPs.getPkg();
+ if (dataOwnerPs == null) {
+ if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
+ String errorMsg = "Required installed version code was "
+ + requiredInstalledVersionCode
+ + " but package is not installed";
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
}
+ // The package doesn't exist in the system, don't need to check the version
+ // replacing.
+ return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
}
+ // Package which currently owns the data that the new package will own if installed.
+ // If an app is uninstalled while keeping data (e.g. adb uninstall -k), dataOwnerPkg
+ // will be null whereas dataOwnerPs will contain information about the package
+ // which was uninstalled while keeping its data. The AndroidPackage object that the
+ // PackageSetting refers to is the same object that is stored in mPackages.
+ AndroidPackage dataOwnerPkg = dataOwnerPs.getPkg();
+
if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
if (dataOwnerPkg == null) {
String errorMsg = "Required installed version code was "
@@ -2662,7 +2672,27 @@
}
}
- if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
+ // If dataOwnerPkg is null but dataOwnerPs is not null, there is always data on
+ // some users. Wwe should do the downgrade check. E.g. DELETE_KEEP_DATA and
+ // archived apps
+ if (dataOwnerPkg == null) {
+ if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
+ dataOwnerPs.isDebuggable())) {
+ // The data exists on some users and downgrade is not permitted; a lower
+ // version of the app will not be allowed.
+ try {
+ PackageManagerServiceUtils.checkDowngrade(dataOwnerPs, pkgLite);
+ } catch (PackageManagerException e) {
+ String errorMsg = "Downgrade detected on app uninstalled with"
+ + " DELETE_KEEP_DATA: " + e.getMessage();
+ Slog.w(TAG, errorMsg);
+ return Pair.create(
+ PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+ }
+ }
+ // dataOwnerPs.getPkg() is not null on system apps case. Don't need to consider
+ // system apps case like below.
+ } else if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
dataOwnerPkg.isDebuggable())) {
// Downgrade is not permitted; a lower version of the app will not be allowed
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 924b36c..c3cac20 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1419,10 +1419,23 @@
/**
* Check and throw if the given before/after packages would be considered a
- * downgrade.
+ * downgrade with {@link PackageSetting}.
*/
- public static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
- throws PackageManagerException {
+ public static void checkDowngrade(@NonNull PackageSetting before,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
+ if (after.getLongVersionCode() < before.getVersionCode()) {
+ throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+ "Update version code " + after.versionCode + " is older than current "
+ + before.getVersionCode());
+ }
+ }
+
+ /**
+ * Check and throw if the given before/after packages would be considered a
+ * downgrade with {@link AndroidPackage}.
+ */
+ public static void checkDowngrade(@NonNull AndroidPackage before,
+ @NonNull PackageInfoLite after) throws PackageManagerException {
if (after.getLongVersionCode() < before.getLongVersionCode()) {
throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
"Update version code " + after.versionCode + " is older than current "
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7870b17..82df527 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,6 +97,7 @@
FORCE_QUERYABLE_OVERRIDE,
SCANNED_AS_STOPPED_SYSTEM_APP,
PENDING_RESTORE,
+ DEBUGGABLE,
})
public @interface Flags {
}
@@ -105,6 +106,7 @@
private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
private static final int PENDING_RESTORE = 1 << 4;
+ private static final int DEBUGGABLE = 1 << 5;
}
private int mBooleans;
@@ -562,6 +564,20 @@
return getBoolean(Booleans.PENDING_RESTORE);
}
+ /**
+ * @see PackageState#isDebuggable
+ */
+ public PackageSetting setDebuggable(boolean value) {
+ setBoolean(Booleans.DEBUGGABLE, value);
+ onChanged();
+ return this;
+ }
+
+ @Override
+ public boolean isDebuggable() {
+ return getBoolean(Booleans.DEBUGGABLE);
+ }
+
@Override
public String toString() {
return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 9ab6016..d8ce38e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,6 +437,9 @@
pkgSetting.setIsOrphaned(true);
}
+ // update debuggable to packageSetting
+ pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+
// Take care of first install / last update times.
final long scanFileTime = getLastModifiedTime(parsedPackage);
final long existingFirstInstallTime = userId == UserHandle.USER_ALL
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3956552..0d16b00 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3255,6 +3255,9 @@
if (pkg.isPendingRestore()) {
serializer.attributeBoolean(null, "pendingRestore", true);
}
+ if (pkg.isDebuggable()) {
+ serializer.attributeBoolean(null, "debuggable", true);
+ }
if (pkg.isLoading()) {
serializer.attributeBoolean(null, "isLoading", true);
}
@@ -3269,7 +3272,6 @@
serializer.attributeInt(null, "appMetadataSource",
pkg.getAppMetadataSource());
-
writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
@@ -4059,6 +4061,7 @@
long versionCode = 0;
boolean installedForceQueryable = false;
boolean isPendingRestore = false;
+ boolean isDebuggable = false;
float loadingProgress = 0;
long loadingCompletedTime = 0;
UUID domainSetId;
@@ -4085,6 +4088,7 @@
updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
isPendingRestore = parser.getAttributeBoolean(null, "pendingRestore", false);
+ isDebuggable = parser.getAttributeBoolean(null, "debuggable", false);
loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0);
@@ -4259,6 +4263,7 @@
.setUpdateAvailable(updateAvailable)
.setForceQueryableOverride(installedForceQueryable)
.setPendingRestore(isPendingRestore)
+ .setDebuggable(isDebuggable)
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
.setAppMetadataFilePath(appMetadataFilePath)
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e0ee199..5876188 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -274,6 +274,14 @@
boolean isPendingRestore();
/**
+ * @see ApplicationInfo#FLAG_DEBUGGABLE
+ * @see R.styleable#AndroidManifestApplication_debuggable
+ * @see AndroidPackage#isDebuggable
+ * @hide
+ */
+ boolean isDebuggable();
+
+ /**
* Retrieves the shared user app ID. Note that the actual shared user data is not available here
* and must be queried separately.
*
diff --git a/services/core/java/com/android/server/updates/Android.bp b/services/core/java/com/android/server/updates/Android.bp
new file mode 100644
index 0000000..10beebb
--- /dev/null
+++ b/services/core/java/com/android/server/updates/Android.bp
@@ -0,0 +1,11 @@
+aconfig_declarations {
+ name: "updates_flags",
+ package: "com.android.server.updates",
+ container: "system",
+ srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "updates_flags_lib",
+ aconfig_declarations: "updates_flags",
+}
diff --git a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
index 5565b6f..af4025e 100644
--- a/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/CertificateTransparencyLogInstallReceiver.java
@@ -16,17 +16,15 @@
package com.android.server.updates;
+import android.content.Context;
+import android.content.Intent;
import android.os.FileUtils;
import android.system.ErrnoException;
import android.system.Os;
-import android.util.Base64;
import android.util.Slog;
-import com.android.internal.util.HexDump;
-
import libcore.io.Streams;
-import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -36,10 +34,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
public class CertificateTransparencyLogInstallReceiver extends ConfigUpdateInstallReceiver {
@@ -52,31 +47,31 @@
@Override
protected void install(InputStream inputStream, int version) throws IOException {
- /* Install is complicated here because we translate the input, which is a JSON file
- * containing log information to a directory with a file per log. To support atomically
- * replacing the old configuration directory with the new there's a bunch of steps. We
- * create a new directory with the logs and then do an atomic update of the current symlink
- * to point to the new directory.
- */
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ // To support atomically replacing the old configuration directory with the new there's a
+ // bunch of steps. We create a new directory with the logs and then do an atomic update of
+ // the current symlink to point to the new directory.
// 1. Ensure that the update dir exists and is readable
updateDir.mkdir();
if (!updateDir.isDirectory()) {
throw new IOException("Unable to make directory " + updateDir.getCanonicalPath());
}
if (!updateDir.setReadable(true, false)) {
- throw new IOException("Unable to set permissions on " +
- updateDir.getCanonicalPath());
+ throw new IOException("Unable to set permissions on " + updateDir.getCanonicalPath());
}
File currentSymlink = new File(updateDir, "current");
File newVersion = new File(updateDir, LOGDIR_PREFIX + String.valueOf(version));
- File oldDirectory;
// 2. Handle the corner case where the new directory already exists.
if (newVersion.exists()) {
// If the symlink has already been updated then the update died between steps 7 and 8
// and so we cannot delete the directory since its in use. Instead just bump the version
// and return.
if (newVersion.getCanonicalPath().equals(currentSymlink.getCanonicalPath())) {
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
deleteOldLogDirectories();
return;
@@ -91,22 +86,12 @@
throw new IOException("Unable to make directory " + newVersion.getCanonicalPath());
}
if (!newVersion.setReadable(true, false)) {
- throw new IOException("Failed to set " +newVersion.getCanonicalPath() +
- " readable");
+ throw new IOException(
+ "Failed to set " + newVersion.getCanonicalPath() + " readable");
}
- // 4. For each log in the log file create the corresponding file in <new_version>/ .
- try {
- byte[] content = Streams.readFullyNoClose(inputStream);
- JSONObject json = new JSONObject(new String(content, StandardCharsets.UTF_8));
- JSONArray logs = json.getJSONArray("logs");
- for (int i = 0; i < logs.length(); i++) {
- JSONObject log = logs.getJSONObject(i);
- installLog(newVersion, log);
- }
- } catch (JSONException e) {
- throw new IOException("Failed to parse logs", e);
- }
+ // 4. Validate the log list json and move the file in <new_version>/ .
+ installLogList(newVersion, inputStream);
// 5. Create the temp symlink. We'll rename this to the target symlink to get an atomic
// update.
@@ -125,49 +110,38 @@
}
Slog.i(TAG, "CT log directory updated to " + newVersion.getAbsolutePath());
// 7. Update the current version information
- writeUpdate(updateDir, updateVersion,
+ writeUpdate(
+ updateDir,
+ updateVersion,
new ByteArrayInputStream(Long.toString(version).getBytes()));
// 8. Cleanup
deleteOldLogDirectories();
}
- private void installLog(File directory, JSONObject logObject) throws IOException {
+ @Override
+ protected void postInstall(Context context, Intent intent) {
+ if (!Flags.certificateTransparencyInstaller()) {
+ return;
+ }
+ }
+
+ private void installLogList(File directory, InputStream inputStream) throws IOException {
try {
- String logFilename = getLogFileName(logObject.getString("key"));
- File file = new File(directory, logFilename);
- try (OutputStreamWriter out =
- new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
- writeLogEntry(out, "key", logObject.getString("key"));
- writeLogEntry(out, "url", logObject.getString("url"));
- writeLogEntry(out, "description", logObject.getString("description"));
+ byte[] content = Streams.readFullyNoClose(inputStream);
+ if (new JSONObject(new String(content, StandardCharsets.UTF_8)).length() == 0) {
+ throw new IOException("Log list data not valid");
+ }
+
+ File file = new File(directory, "log_list.json");
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(content);
}
if (!file.setReadable(true, false)) {
throw new IOException("Failed to set permissions on " + file.getCanonicalPath());
}
} catch (JSONException e) {
- throw new IOException("Failed to parse log", e);
+ throw new IOException("Malformed json in log list", e);
}
-
- }
-
- /**
- * Get the filename for a log based on its public key. This must be kept in sync with
- * org.conscrypt.ct.CTLogStoreImpl.
- */
- private String getLogFileName(String base64PublicKey) {
- byte[] keyBytes = Base64.decode(base64PublicKey, Base64.DEFAULT);
- try {
- byte[] id = MessageDigest.getInstance("SHA-256").digest(keyBytes);
- return HexDump.toHexString(id, false);
- } catch (NoSuchAlgorithmException e) {
- // SHA-256 is guaranteed to be available.
- throw new RuntimeException(e);
- }
- }
-
- private void writeLogEntry(OutputStreamWriter out, String key, String value)
- throws IOException {
- out.write(key + ":" + value + "\n");
}
private void deleteOldLogDirectories() throws IOException {
@@ -175,12 +149,14 @@
return;
}
File currentTarget = new File(updateDir, "current").getCanonicalFile();
- FileFilter filter = new FileFilter() {
- @Override
- public boolean accept(File file) {
- return !currentTarget.equals(file) && file.getName().startsWith(LOGDIR_PREFIX);
- }
- };
+ FileFilter filter =
+ new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !currentTarget.equals(file)
+ && file.getName().startsWith(LOGDIR_PREFIX);
+ }
+ };
for (File f : updateDir.listFiles(filter)) {
FileUtils.deleteContentsAndDir(f);
}
diff --git a/services/core/java/com/android/server/updates/flags.aconfig b/services/core/java/com/android/server/updates/flags.aconfig
new file mode 100644
index 0000000..476cb37
--- /dev/null
+++ b/services/core/java/com/android/server/updates/flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.updates"
+container: "system"
+
+flag {
+ name: "certificate_transparency_installer"
+ is_exported: true
+ namespace: "network_security"
+ description: "Enable certificate transparency installer for log list data"
+ bug: "319829948"
+}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 5be5bc5..2c73412 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -47,7 +47,7 @@
import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowTracing.WINSCOPE_EXT;
+import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
import android.animation.ObjectAnimator;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a1e71a8..8768074 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1102,11 +1102,9 @@
pw.println(prefix + "mLastReportedConfigurations:");
mLastReportedConfiguration.dump(pw, prefix + " ");
- if (Flags.activityWindowInfoFlag()) {
- pw.print(prefix);
- pw.print("mLastReportedActivityWindowInfo=");
- pw.println(mLastReportedActivityWindowInfo);
- }
+ pw.print(prefix);
+ pw.print("mLastReportedActivityWindowInfo=");
+ pw.println(mLastReportedActivityWindowInfo);
pw.print(prefix); pw.print("CurrentConfiguration="); pw.println(getConfiguration());
if (!getRequestedOverrideConfiguration().equals(EMPTY)) {
@@ -3161,7 +3159,7 @@
@NonNull
ActivityWindowInfo getActivityWindowInfo() {
- if (!Flags.activityWindowInfoFlag() || !isAttached()) {
+ if (!isAttached()) {
return mTmpActivityWindowInfo;
}
if (isFixedRotationTransforming()) {
@@ -8284,9 +8282,7 @@
}
void setLastReportedActivityWindowInfo(@NonNull ActivityWindowInfo activityWindowInfo) {
- if (Flags.activityWindowInfoFlag()) {
- mLastReportedActivityWindowInfo.set(activityWindowInfo);
- }
+ mLastReportedActivityWindowInfo.set(activityWindowInfo);
}
@Nullable
@@ -9702,8 +9698,8 @@
// the combine configurations are equal, but would otherwise differ in the override config
mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo();
- final boolean isActivityWindowInfoChanged = Flags.activityWindowInfoFlag()
- && !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo);
+ final boolean isActivityWindowInfoChanged =
+ !mLastReportedActivityWindowInfo.equals(newActivityWindowInfo);
if (!displayChanged && !isActivityWindowInfoChanged
&& getConfiguration().equals(mTmpConfig)) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration & display "
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index de73e6c..228eb76 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1101,7 +1101,7 @@
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
- mLastReportedActivityWindowInfo = Flags.activityWindowInfoFlag() && mActivityRecord != null
+ mLastReportedActivityWindowInfo = mActivityRecord != null
? new ActivityWindowInfo()
: null;
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index ba5323e..21f7eca 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -18,89 +18,52 @@
import static android.os.Build.IS_USER;
-import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
-import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
import static com.android.server.wm.WindowManagerTraceProto.WHERE;
import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
import android.annotation.Nullable;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.ProtoLog;
-import com.android.internal.util.TraceBuffer;
-import java.io.File;
-import java.io.IOException;
import java.io.PrintWriter;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A class that allows window manager to dump its state continuously to a trace file, such that a
+ * A class that allows window manager to dump its state continuously, such that a
* time series of window manager state can be analyzed after the fact.
*/
-class WindowTracing {
-
- /**
- * Maximum buffer size, currently defined as 5 MB
- * Size was experimentally defined to fit between 100 to 150 elements.
- */
- private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
- private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
- private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
- static final String WINSCOPE_EXT = ".winscope";
- private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
- private static final String TAG = "WindowTracing";
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+abstract class WindowTracing {
+ protected static final String TAG = "WindowTracing";
+ protected static final String WHERE_START_TRACING = "trace.enable";
+ protected static final String WHERE_ON_FRAME = "onFrame";
private final WindowManagerService mService;
private final Choreographer mChoreographer;
private final WindowManagerGlobalLock mGlobalLock;
- private final Object mEnabledLock = new Object();
- private final File mTraceFile;
- private final TraceBuffer mBuffer;
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
- log("onFrame" /* where */);
+ log(WHERE_ON_FRAME);
- private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
- private boolean mLogOnFrame = false;
- private boolean mEnabled;
- private volatile boolean mEnabledLockFree;
- private boolean mScheduled;
+ private AtomicBoolean mScheduled = new AtomicBoolean(false);
- private final IProtoLog mProtoLog;
static WindowTracing createDefaultAndStartLooper(WindowManagerService service,
Choreographer choreographer) {
- File file = new File(TRACE_FILENAME);
- return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM);
+ return new WindowTracingLegacy(service, choreographer);
}
- private WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- int bufferCapacity) {
- this(file, service, choreographer, service.mGlobalLock, bufferCapacity);
- }
-
- WindowTracing(File file, WindowManagerService service, Choreographer choreographer,
- WindowManagerGlobalLock globalLock, int bufferCapacity) {
+ protected WindowTracing(WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock) {
mChoreographer = choreographer;
mService = service;
mGlobalLock = globalLock;
- mTraceFile = file;
- mBuffer = new TraceBuffer(bufferCapacity);
- setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
- mProtoLog = ProtoLog.getSingleInstance();
}
void startTrace(@Nullable PrintWriter pw) {
@@ -108,44 +71,29 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
- log("trace.enable");
+ startTraceInternal(pw);
}
- /**
- * Stops the trace and write the current buffer to disk
- * @param pw Print writer
- */
void stopTrace(@Nullable PrintWriter pw) {
if (IS_USER) {
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- mEnabled = mEnabledLockFree = false;
-
- if (mEnabled) {
- logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
- throw new IllegalStateException("tracing enabled while waiting for flush.");
- }
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- }
if (!android.tracing.Flags.perfettoProtologTracing()) {
((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
}
+ stopTraceInternal(pw);
}
/**
- * Stops the trace and write the current buffer to disk then restart, if it's already running.
+ * If legacy tracing is enabled (either WM or ProtoLog):
+ * 1. Stop tracing
+ * 2. Write trace to disk (to be picked by dumpstate)
+ * 3. Restart tracing
+ *
* @param pw Print writer
*/
void saveForBugreport(@Nullable PrintWriter pw) {
@@ -153,143 +101,24 @@
logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
return;
}
- synchronized (mEnabledLock) {
- if (!mEnabled) {
- return;
- }
- mEnabled = mEnabledLockFree = false;
- logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
- writeTraceToFileLocked();
- logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).stopProtoLog(pw, true);
- }
- logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
- mBuffer.resetBuffer();
- mEnabled = mEnabledLockFree = true;
- if (!android.tracing.Flags.perfettoProtologTracing()) {
- ((LegacyProtoLogImpl) mProtoLog).startProtoLog(pw);
- }
+ if (!android.tracing.Flags.perfettoProtologTracing()
+ && ProtoLog.getSingleInstance().isProtoEnabled()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
}
+ saveForBugreportInternal(pw);
}
- private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
- mLogLevel = logLevel;
-
- switch (logLevel) {
- case WindowTraceLogLevel.ALL: {
- setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
- break;
- }
- case WindowTraceLogLevel.TRIM: {
- setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
- break;
- }
- case WindowTraceLogLevel.CRITICAL: {
- setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
- break;
- }
- }
- }
-
- private void setLogFrequency(boolean onFrame, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing log frequency to "
- + ((onFrame) ? "frame" : "transaction"));
- mLogOnFrame = onFrame;
- }
-
- private void setBufferCapacity(int capacity, PrintWriter pw) {
- logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
- mBuffer.setCapacity(capacity);
- }
-
- boolean isEnabled() {
- return mEnabledLockFree;
- }
-
- int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArgRequired();
- switch (cmd) {
- case "start":
- startTrace(pw);
- return 0;
- case "stop":
- stopTrace(pw);
- return 0;
- case "save-for-bugreport":
- saveForBugreport(pw);
- return 0;
- case "status":
- logAndPrintln(pw, getStatus());
- return 0;
- case "frame":
- setLogFrequency(true /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "transaction":
- setLogFrequency(false /* onFrame */, pw);
- mBuffer.resetBuffer();
- return 0;
- case "level":
- String logLevelStr = shell.getNextArgRequired().toLowerCase();
- switch (logLevelStr) {
- case "all": {
- setLogLevel(WindowTraceLogLevel.ALL, pw);
- break;
- }
- case "trim": {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- case "critical": {
- setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
- break;
- }
- default: {
- setLogLevel(WindowTraceLogLevel.TRIM, pw);
- break;
- }
- }
- mBuffer.resetBuffer();
- return 0;
- case "size":
- setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
- mBuffer.resetBuffer();
- return 0;
- default:
- pw.println("Unknown command: " + cmd);
- pw.println("Window manager trace options:");
- pw.println(" start: Start logging");
- pw.println(" stop: Stop logging");
- pw.println(" save-for-bugreport: Save logging data to file if it's running.");
- pw.println(" frame: Log trace once per frame");
- pw.println(" transaction: Log each transaction");
- pw.println(" size: Set the maximum log size (in KB)");
- pw.println(" status: Print trace status");
- pw.println(" level [lvl]: Set the log level between");
- pw.println(" lvl may be one of:");
- pw.println(" critical: Only visible windows with reduced information");
- pw.println(" trim: All windows with reduced");
- pw.println(" all: All window and information");
- return -1;
- }
- }
-
- String getStatus() {
- return "Status: "
- + ((isEnabled()) ? "Enabled" : "Disabled")
- + "\n"
- + "Log level: "
- + mLogLevel
- + "\n"
- + mBuffer.getStatus();
- }
+ abstract void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw);
+ abstract void setLogFrequency(boolean onFrame, PrintWriter pw);
+ abstract void setBufferCapacity(int capacity, PrintWriter pw);
+ abstract boolean isEnabled();
+ abstract int onShellCommand(ShellCommand shell);
+ abstract String getStatus();
/**
* If tracing is enabled, log the current state or schedule the next frame to be logged,
- * according to {@link #mLogOnFrame}.
+ * according to the configuration in the derived tracing class.
*
* @param where Logging point descriptor
*/
@@ -298,59 +127,63 @@
return;
}
- if (mLogOnFrame) {
- schedule();
- } else {
+ if (shouldLogOnTransaction()) {
log(where);
}
+
+ if (shouldLogOnFrame()) {
+ schedule();
+ }
}
/**
* Schedule the log to trace the next frame
*/
private void schedule() {
- if (mScheduled) {
+ if (!mScheduled.compareAndSet(false, true)) {
return;
}
- mScheduled = true;
mChoreographer.postFrameCallback(mFrameCallback);
}
/**
- * Write the current frame to the buffer
+ * Write the current frame to proto
*
+ * @param os Proto stream buffer
+ * @param logLevel Log level
* @param where Logging point descriptor
+ * @param elapsedRealtimeNanos Timestamp
*/
- private void log(String where) {
+ protected void dumpToProto(ProtoOutputStream os, @WindowTraceLogLevel int logLevel,
+ String where, long elapsedRealtimeNanos) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
try {
- ProtoOutputStream os = new ProtoOutputStream();
- long tokenOuter = os.start(ENTRY);
- os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+ os.write(ELAPSED_REALTIME_NANOS, elapsedRealtimeNanos);
os.write(WHERE, where);
- long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
+ long token = os.start(WINDOW_MANAGER_SERVICE);
synchronized (mGlobalLock) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "dumpDebugLocked");
try {
- mService.dumpDebugLocked(os, mLogLevel);
+ mService.dumpDebugLocked(os, logLevel);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- os.end(tokenInner);
- os.end(tokenOuter);
- mBuffer.add(os);
- mScheduled = false;
+ os.end(token);
} catch (Exception e) {
Log.wtf(TAG, "Exception while tracing state", e);
} finally {
+ boolean isOnFrameLogEvent = where == WHERE_ON_FRAME;
+ if (isOnFrameLogEvent) {
+ mScheduled.set(false);
+ }
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+ protected void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Log.i(TAG, msg);
if (pw != null) {
pw.println(msg);
@@ -358,24 +191,10 @@
}
}
- /**
- * Writes the trace buffer to disk. This method has no internal synchronization and should be
- * externally synchronized
- */
- private void writeTraceToFileLocked() {
- try {
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
- ProtoOutputStream proto = new ProtoOutputStream();
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- long timeOffsetNs =
- TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
- - SystemClock.elapsedRealtimeNanos();
- proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
- mBuffer.writeTraceToFile(mTraceFile, proto);
- } catch (IOException e) {
- Log.e(TAG, "Unable to write buffer to file", e);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
- }
+ protected abstract void startTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void stopTraceInternal(@Nullable PrintWriter pw);
+ protected abstract void saveForBugreportInternal(@Nullable PrintWriter pw);
+ protected abstract void log(String where);
+ protected abstract boolean shouldLogOnFrame();
+ protected abstract boolean shouldLogOnTransaction();
}
diff --git a/services/core/java/com/android/server/wm/WindowTracingLegacy.java b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
new file mode 100644
index 0000000..7a36707
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
@@ -0,0 +1,276 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.WindowManagerTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+
+import com.android.internal.util.TraceBuffer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+class WindowTracingLegacy extends WindowTracing {
+
+ /**
+ * Maximum buffer size, currently defined as 5 MB
+ * Size was experimentally defined to fit between 100 to 150 elements.
+ */
+ private static final int BUFFER_CAPACITY_CRITICAL = 5120 * 1024; // 5 MB
+ private static final int BUFFER_CAPACITY_TRIM = 10240 * 1024; // 10 MB
+ private static final int BUFFER_CAPACITY_ALL = 20480 * 1024; // 20 MB
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace" + WINSCOPE_EXT;
+ private static final String TAG = "WindowTracing";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final Object mEnabledLock = new Object();
+ private final File mTraceFile;
+ private final TraceBuffer mBuffer;
+
+ private boolean mEnabled;
+ private volatile boolean mEnabledLockFree;
+
+ protected @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
+ protected boolean mLogOnFrame = false;
+
+ WindowTracingLegacy(WindowManagerService service, Choreographer choreographer) {
+ this(new File(TRACE_FILENAME), service, choreographer,
+ service.mGlobalLock, BUFFER_CAPACITY_TRIM);
+ }
+
+ WindowTracingLegacy(File traceFile, WindowManagerService service, Choreographer choreographer,
+ WindowManagerGlobalLock globalLock, int bufferSize) {
+ super(service, choreographer, globalLock);
+ mTraceFile = traceFile;
+ mBuffer = new TraceBuffer(bufferSize);
+ }
+
+ @Override
+ void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
+ mLogLevel = logLevel;
+
+ switch (logLevel) {
+ case WindowTraceLogLevel.ALL: {
+ setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
+ break;
+ }
+ case WindowTraceLogLevel.TRIM: {
+ setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
+ break;
+ }
+ case WindowTraceLogLevel.CRITICAL: {
+ setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
+ break;
+ }
+ }
+ }
+
+ @Override
+ void setLogFrequency(boolean onFrame, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing log frequency to "
+ + ((onFrame) ? "frame" : "transaction"));
+ mLogOnFrame = onFrame;
+ }
+
+ @Override
+ void setBufferCapacity(int capacity, PrintWriter pw) {
+ logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes");
+ mBuffer.setCapacity(capacity);
+ }
+
+ @Override
+ boolean isEnabled() {
+ return mEnabledLockFree;
+ }
+
+ @Override
+ int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArgRequired();
+ switch (cmd) {
+ case "start":
+ startTrace(pw);
+ return 0;
+ case "stop":
+ stopTrace(pw);
+ return 0;
+ case "save-for-bugreport":
+ saveForBugreport(pw);
+ return 0;
+ case "status":
+ logAndPrintln(pw, getStatus());
+ return 0;
+ case "frame":
+ setLogFrequency(true /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "transaction":
+ setLogFrequency(false /* onFrame */, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ case "level":
+ String logLevelStr = shell.getNextArgRequired().toLowerCase();
+ switch (logLevelStr) {
+ case "all": {
+ setLogLevel(WindowTraceLogLevel.ALL, pw);
+ break;
+ }
+ case "trim": {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ case "critical": {
+ setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
+ break;
+ }
+ default: {
+ setLogLevel(WindowTraceLogLevel.TRIM, pw);
+ break;
+ }
+ }
+ mBuffer.resetBuffer();
+ return 0;
+ case "size":
+ setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw);
+ mBuffer.resetBuffer();
+ return 0;
+ default:
+ pw.println("Unknown command: " + cmd);
+ pw.println("Window manager trace options:");
+ pw.println(" start: Start logging");
+ pw.println(" stop: Stop logging");
+ pw.println(" save-for-bugreport: Save logging data to file if it's running.");
+ pw.println(" frame: Log trace once per frame");
+ pw.println(" transaction: Log each transaction");
+ pw.println(" size: Set the maximum log size (in KB)");
+ pw.println(" status: Print trace status");
+ pw.println(" level [lvl]: Set the log level between");
+ pw.println(" lvl may be one of:");
+ pw.println(" critical: Only visible windows with reduced information");
+ pw.println(" trim: All windows with reduced");
+ pw.println(" all: All window and information");
+ return -1;
+ }
+ }
+
+ @Override
+ String getStatus() {
+ return "Status: "
+ + ((isEnabled()) ? "Enabled" : "Disabled")
+ + "\n"
+ + "Log level: "
+ + mLogLevel
+ + "\n"
+ + mBuffer.getStatus();
+ }
+
+ @Override
+ protected void startTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ log(WHERE_START_TRACING);
+ }
+
+ @Override
+ protected void stopTraceInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ mEnabled = mEnabledLockFree = false;
+
+ if (mEnabled) {
+ logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
+ throw new IllegalStateException("tracing enabled while waiting for flush.");
+ }
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ }
+ }
+
+ @Override
+ protected void saveForBugreportInternal(@Nullable PrintWriter pw) {
+ synchronized (mEnabledLock) {
+ if (!mEnabled) {
+ return;
+ }
+ mEnabled = mEnabledLockFree = false;
+ logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
+ writeTraceToFileLocked();
+ logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
+ logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
+ mBuffer.resetBuffer();
+ mEnabled = mEnabledLockFree = true;
+ }
+ }
+
+ @Override
+ protected void log(String where) {
+ try {
+ ProtoOutputStream os = new ProtoOutputStream();
+ long token = os.start(ENTRY);
+ dumpToProto(os, mLogLevel, where, SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ mBuffer.add(os);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Exception while tracing state", e);
+ }
+ }
+
+ @Override
+ protected boolean shouldLogOnFrame() {
+ return mLogOnFrame;
+ }
+
+ @Override
+ protected boolean shouldLogOnTransaction() {
+ return !mLogOnFrame;
+ }
+
+ private void writeTraceToFileLocked() {
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ long timeOffsetNs =
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())
+ - SystemClock.elapsedRealtimeNanos();
+ proto.write(REAL_TO_ELAPSED_TIME_OFFSET_NANOS, timeOffsetNs);
+ mBuffer.writeTraceToFile(mTraceFile, proto);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to write buffer to file", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 89b4aea..7138306 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1027,6 +1027,25 @@
}
@Test
+ public void testWriteReadDebuggable() {
+ Settings settings = makeSettings();
+ PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+ packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+ packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+ .setUid(packageSetting.getAppId())
+ .hideAsFinal());
+
+ packageSetting.setDebuggable(true);
+ settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+ settings.writeLPr(computer, /* sync= */ true);
+ settings.mPackages.clear();
+
+ assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+ assertThat(settings.getPackageLPr(PACKAGE_NAME_1).isDebuggable(), is(true));
+ }
+
+ @Test
public void testWriteReadArchiveState() {
Settings settings = makeSettings();
PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index fcf7a3f..4958b90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1294,8 +1294,6 @@
@Test
public void testRelayout_appWindowSendActivityWindowInfo() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
-
// Skip unnecessary operations of relayout.
spyOn(mWm.mWindowPlacerLocked);
doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
similarity index 97%
rename from services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
rename to services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index c183403..48a8d55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -63,7 +63,7 @@
*/
@SmallTest
@Presubmit
-public class WindowTracingTest {
+public class WindowTracingLegacyTest {
private static final byte[] MAGIC_HEADER = new byte[]{
0x9, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45,
@@ -88,7 +88,7 @@
mFile = testContext.getFileStreamPath("tracing_test.dat");
mFile.delete();
- mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer,
+ mWindowTracing = new WindowTracingLegacy(mFile, mWmMock, mChoreographer,
new WindowManagerGlobalLock(), 1024);
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..2539653
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LegacyProtoLogViewerConfigReaderTest {
+ private static final String TEST_VIEWER_CONFIG = "{\n"
+ + " \"version\": \"1.0.0\",\n"
+ + " \"messages\": {\n"
+ + " \"70933285\": {\n"
+ + " \"message\": \"Test completed successfully: %b\",\n"
+ + " \"level\": \"ERROR\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1792430067\": {\n"
+ + " \"message\": \"Attempted to add window to a display that does not exist: %d."
+ + " Aborting.\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1352021864\": {\n"
+ + " \"message\": \"Test 2\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"409412266\": {\n"
+ + " \"message\": \"Window %s is already added\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"groups\": {\n"
+ + " \"GENERIC_WM\": {\n"
+ + " \"tag\": \"WindowManager\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+
+ private LegacyProtoLogViewerConfigReader
+ mConfig = new LegacyProtoLogViewerConfigReader();
+ private File mTestViewerConfig;
+
+ @Before
+ public void setUp() throws IOException {
+ mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+ writer.write(TEST_VIEWER_CONFIG);
+ writer.close();
+ }
+
+ @After
+ public void tearDown() {
+ //noinspection ResultOfMethodCallIgnored
+ mTestViewerConfig.delete();
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
+ assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+ assertEquals("Test 2", mConfig.getViewerString(1352021864));
+ assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig_invalidFile() {
+ mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
+ // No exception is thrown.
+ assertNull(mConfig.getViewerString(1));
+ }
+}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
index dbd85d3..be0e8bc 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java
@@ -20,75 +20,77 @@
import static org.junit.Assert.assertNull;
import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.zip.GZIPOutputStream;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
-@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class ProtoLogViewerConfigReaderTest {
- private static final String TEST_VIEWER_CONFIG = "{\n"
- + " \"version\": \"1.0.0\",\n"
- + " \"messages\": {\n"
- + " \"70933285\": {\n"
- + " \"message\": \"Test completed successfully: %b\",\n"
- + " \"level\": \"ERROR\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1792430067\": {\n"
- + " \"message\": \"Attempted to add window to a display that does not exist: %d."
- + " Aborting.\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"1352021864\": {\n"
- + " \"message\": \"Test 2\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " },\n"
- + " \"409412266\": {\n"
- + " \"message\": \"Window %s is already added\",\n"
- + " \"level\": \"WARN\",\n"
- + " \"group\": \"GENERIC_WM\"\n"
- + " }\n"
- + " },\n"
- + " \"groups\": {\n"
- + " \"GENERIC_WM\": {\n"
- + " \"tag\": \"WindowManager\"\n"
- + " }\n"
- + " }\n"
- + "}\n";
+ private static final String TEST_GROUP_NAME = "MY_TEST_GROUP";
+ private static final String TEST_GROUP_TAG = "TEST";
+ private static final String OTHER_TEST_GROUP_NAME = "MY_OTHER_TEST_GROUP";
+ private static final String OTHER_TEST_GROUP_TAG = "OTHER_TEST";
- private LegacyProtoLogViewerConfigReader
- mConfig = new LegacyProtoLogViewerConfigReader();
- private File mTestViewerConfig;
+ private static final byte[] TEST_VIEWER_CONFIG =
+ perfetto.protos.Protolog.ProtoLogViewerConfig.newBuilder()
+ .addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(TEST_GROUP_NAME)
+ .setTag(TEST_GROUP_TAG)
+ ).addGroups(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder()
+ .setId(1)
+ .setName(OTHER_TEST_GROUP_NAME)
+ .setTag(OTHER_TEST_GROUP_TAG)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(1)
+ .setMessage("My Test Log Message 1 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_DEBUG)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(2)
+ .setMessage("My Test Log Message 2 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_VERBOSE)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(3)
+ .setMessage("My Test Log Message 3 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WARN)
+ .setGroupId(1)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(4)
+ .setMessage("My Test Log Message 4 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_ERROR)
+ .setGroupId(2)
+ ).addMessages(
+ perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
+ .setMessageId(5)
+ .setMessage("My Test Log Message 5 %b")
+ .setLevel(ProtologCommon.ProtoLogLevel.PROTOLOG_LEVEL_WTF)
+ .setGroupId(2)
+ ).build().toByteArray();
+
+ private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider =
+ () -> new ProtoInputStream(TEST_VIEWER_CONFIG);
+
+ private ProtoLogViewerConfigReader mConfig;
@Before
- public void setUp() throws IOException {
- mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
- OutputStreamWriter writer = new OutputStreamWriter(
- new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
- writer.write(TEST_VIEWER_CONFIG);
- writer.close();
- }
-
- @After
- public void tearDown() {
- //noinspection ResultOfMethodCallIgnored
- mTestViewerConfig.delete();
+ public void before() {
+ mConfig = new ProtoLogViewerConfigReader(mViewerConfigInputStreamProvider);
}
@Test
@@ -98,17 +100,26 @@
@Test
public void loadViewerConfig() {
- mConfig.loadViewerConfig(msg -> {}, mTestViewerConfig.getAbsolutePath());
- assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
- assertEquals("Test 2", mConfig.getViewerString(1352021864));
- assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
- assertNull(mConfig.getViewerString(1));
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME });
+ assertEquals("My Test Log Message 1 %b", mConfig.getViewerString(1));
+ assertEquals("My Test Log Message 2 %b", mConfig.getViewerString(2));
+ assertEquals("My Test Log Message 3 %b", mConfig.getViewerString(3));
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
@Test
- public void loadViewerConfig_invalidFile() {
- mConfig.loadViewerConfig(msg -> {}, "/tmp/unknown/file/does/not/exist");
- // No exception is thrown.
+ public void unloadViewerConfig() {
+ mConfig.loadViewerConfig(new String[] { TEST_GROUP_NAME, OTHER_TEST_GROUP_NAME });
+ mConfig.unloadViewerConfig(new String[] { TEST_GROUP_NAME });
assertNull(mConfig.getViewerString(1));
+ assertNull(mConfig.getViewerString(2));
+ assertNull(mConfig.getViewerString(3));
+ assertEquals("My Test Log Message 4 %b", mConfig.getViewerString(4));
+ assertEquals("My Test Log Message 5 %b", mConfig.getViewerString(5));
+
+ mConfig.unloadViewerConfig(new String[] { OTHER_TEST_GROUP_NAME });
+ assertNull(mConfig.getViewerString(4));
+ assertNull(mConfig.getViewerString(5));
}
}