Merge "Clean up activityWindowInfoFlag (2/n)" 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/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c091062..6a96a54 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,7 +60,8 @@
@IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {
BRIGHTNESS_MAX_REASON_NONE,
BRIGHTNESS_MAX_REASON_THERMAL,
- BRIGHTNESS_MAX_REASON_POWER_IC
+ BRIGHTNESS_MAX_REASON_POWER_IC,
+ BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE
})
@Retention(RetentionPolicy.SOURCE)
public @interface BrightnessMaxReason {}
@@ -157,6 +158,8 @@
return "thermal";
case BRIGHTNESS_MAX_REASON_POWER_IC:
return "power IC";
+ case BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE:
+ return "wear bedtime";
}
return "invalid";
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 23e262c..d7952eb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1251,8 +1251,22 @@
* canceled. Only the pilfering window will continue to receive events for the affected pointers
* until the pointer is lifted.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * Furthermore, if any new pointers go down within the touchable region of the pilfering window
+ * and are part of the same gesture, those new pointers will be pilfered as well, and will not
+ * be sent to any other windows.
+ *
+ * Pilfering is designed to be used only once per gesture. Once the gesture is complete
+ * (i.e. on {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_CANCEL},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}), the system will resume dispatching pointers
+ * to the appropriately touched windows.
+ *
+ * NOTE: This method should be used with caution as unexpected pilfering can break fundamental
+ * user interactions.
+ *
+ * NOTE: Since this method pilfers pointers based on gesture stream that is
+ * currently active for the window, the behavior will depend on the state of the system, and
+ * is inherently racy. For example, a pilfer request on a quick tap may not be successful if
+ * the tap is already complete by the time the pilfer request is received by the system.
*
* @see android.os.InputConfig#SPY
* @hide
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 3954bc2..f6f0eff 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -92,3 +92,10 @@
description: "Add a dump capability for attestation_verification service"
bug: "335498868"
}
+
+flag {
+ name: "should_trust_manager_listen_for_primary_auth"
+ namespace: "biometrics"
+ description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
+ bug: "323086607"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0714285..d8a88b8 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -138,12 +138,6 @@
"settings_show_stylus_preferences";
/**
- * Flag to enable/disable biometrics enrollment v2
- * @hide
- */
- public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
-
- /**
* Flag to enable/disable FingerprintSettings v2
* @hide
*/
@@ -223,7 +217,6 @@
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "true");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
- DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index cb5a885..e5be531 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,6 +53,7 @@
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
+import android.view.KeyboardShortcutGroup;
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
@@ -1095,4 +1096,11 @@
boolean transferTouchGesture(in InputTransferToken transferFromToken,
in InputTransferToken transferToToken);
+
+ /**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
}
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 2302dc7..ea4abc1 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -50,13 +50,12 @@
private final SurfaceControl mSurface;
/**
- * Takes all of the current pointer events streams that are currently being sent to this
- * monitor and generates appropriate cancellations for the windows that would normally get
- * them.
+ * Pilfer pointers from this input monitor.
*
- * This method should be used with caution as unexpected pilfering can break fundamental user
- * interactions.
+ * @see android.hardware.input.InputManager#pilferPointers(IBinder)
+ * @deprecated
*/
+ @Deprecated
public void pilferPointers() {
try {
mHost.pilferPointers();
@@ -197,10 +196,10 @@
};
@DataClass.Generated(
- time = 1679692514588L,
+ time = 1720819824835L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\nprivate final @android.annotation.NonNull android.view.SurfaceControl mSurface\npublic @java.lang.Deprecated void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/KeyboardShortcutGroup.aidl b/core/java/android/view/KeyboardShortcutGroup.aidl
new file mode 100644
index 0000000..6f219db
--- /dev/null
+++ b/core/java/android/view/KeyboardShortcutGroup.aidl
@@ -0,0 +1,3 @@
+package android.view;
+
+@JavaOnlyStableParcelable parcelable KeyboardShortcutGroup;
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 3f6fd64..3f49bf3 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -81,12 +81,29 @@
* {@link KeyEvent#META_SYM_ON}.
*/
public KeyboardShortcutInfo(CharSequence label, char baseCharacter, int modifiers) {
+ this(label, null, baseCharacter, modifiers);
+ }
+
+ /**
+ * @param label The label that identifies the action performed by this shortcut.
+ * @param icon An icon that identifies the action performed by this shortcut.
+ * @param baseCharacter The character that triggers the shortcut.
+ * @param modifiers The set of modifiers that, combined with the key, trigger the shortcut.
+ * These should be a combination of {@link KeyEvent#META_CTRL_ON},
+ * {@link KeyEvent#META_SHIFT_ON}, {@link KeyEvent#META_META_ON},
+ * {@link KeyEvent#META_ALT_ON}, {@link KeyEvent#META_FUNCTION_ON} and
+ * {@link KeyEvent#META_SYM_ON}.
+ *
+ * @hide
+ */
+ public KeyboardShortcutInfo(
+ CharSequence label, @Nullable Icon icon, char baseCharacter, int modifiers) {
mLabel = label;
checkArgument(baseCharacter != MIN_VALUE);
mBaseCharacter = baseCharacter;
mKeycode = KeyEvent.KEYCODE_UNKNOWN;
mModifiers = modifiers;
- mIcon = null;
+ mIcon = icon;
}
private KeyboardShortcutInfo(Parcel source) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 18006bb..14978ed 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1668,6 +1668,15 @@
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
/**
+ * Request the application launch keyboard shortcuts the system has defined.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will handle the shortcut.
+ *
+ * @hide
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Request for ime's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index b667427..330e46a 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -237,6 +237,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ try {
+ return WindowManagerGlobal.getWindowManagerService()
+ .getApplicationLaunchKeyboardShortcuts(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public void requestImeKeyboardShortcuts(
final KeyboardShortcutsReceiver receiver, int deviceId) {
IResultReceiver resultReceiver = new IResultReceiver.Stub() {
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..b873175 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,6 +54,12 @@
public class AconfigFlags {
private static final String LOG_TAG = "AconfigFlags";
+ 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");
+
public enum Permission {
READ_WRITE,
READ_ONLY
@@ -67,7 +73,7 @@
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) {
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/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f4ad487..19c6f51 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -22,6 +22,8 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.security.Flags.reportPrimaryAuthAttempts;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -414,7 +416,9 @@
return;
}
getDevicePolicyManager().reportFailedPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(false /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ false, userId);
+ }
}
@UnsupportedAppUsage
@@ -423,7 +427,9 @@
return;
}
getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
- getTrustManager().reportUnlockAttempt(true /* authenticated */, userId);
+ if (!reportPrimaryAuthAttempts() || !shouldTrustManagerListenForPrimaryAuth()) {
+ getTrustManager().reportUnlockAttempt(/* authenticated= */ true, userId);
+ }
}
public void reportPasswordLockout(int timeoutMs, int userId) {
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 982189e..1a1d83c 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1063,8 +1063,8 @@
}
env->ReleaseStringUTFChars(file, file8);
- // Most proc files we read are small, so we only go through the
- // loop once and use the stack buffer. We allocate a buffer big
+ // Most proc files we read are small, so we go through the loop
+ // with the stack buffer firstly. We allocate a buffer big
// enough for the whole file.
char readBufferStack[kProcReadStackBufferSize];
@@ -1072,37 +1072,47 @@
char* readBuffer = &readBufferStack[0];
ssize_t readBufferSize = kProcReadStackBufferSize;
ssize_t numberBytesRead;
+ off_t offset = 0;
for (;;) {
+ ssize_t requestedBufferSize = readBufferSize - offset;
// By using pread, we can avoid an lseek to rewind the FD
// before retry, saving a system call.
- numberBytesRead = pread(fd, readBuffer, readBufferSize, 0);
- if (numberBytesRead < 0 && errno == EINTR) {
- continue;
- }
+ numberBytesRead =
+ TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
if (numberBytesRead < 0) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s fd=%d\n", file8, fd.get());
+ ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
+ strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
+ fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead < readBufferSize) {
+ if (numberBytesRead == 0) {
+ // End of file.
+ numberBytesRead = offset;
break;
}
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ if (numberBytesRead < requestedBufferSize) {
+ // Read less bytes than requested, it's not an error per pread(2).
+ offset += numberBytesRead;
+ } else {
+ // Buffer is fully used, try to grow it.
+ if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
+ readBufferHeap.reset(); // Free address space before getting more.
+ readBufferHeap = std::make_unique<char[]>(readBufferSize);
+ if (!readBufferHeap) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+ return JNI_FALSE;
+ }
+ readBuffer = readBufferHeap.get();
+ offset = 0;
}
- readBufferSize = std::max(readBufferSize * 2,
- kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
}
// parseProcLineArray below modifies the buffer while parsing!
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index cdd8557..61c7a8c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -393,12 +393,6 @@
<bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool>
<java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" />
- <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if
- existing cellular networks already supports IWLAN.
- -->
- <bool name="force_iwlan_mms_feature_enabled">false</bool>
- <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" />
-
<!-- The time duration in millis after which Telephony will abort the last message datagram
sending requests. Telephony starts a timer when receiving a last message datagram sending
request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6b71f97..46b15416 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6534,4 +6534,23 @@
<string name="bg_user_sound_notification_button_mute">Mute</string>
<!-- Notification text to mute the sound from the background user [CHAR LIMIT=NOTIF_BODY]-->
<string name="bg_user_sound_notification_message">Tap to mute sound</string>
+
+ <!-- User visible title for the keyboard shortcut that takes the user to the browser app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_browser">Browser</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the contacts app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_contacts">Contacts</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the email app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_email">Email</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the SMS messaging app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_sms">SMS</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the music app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_music">Music</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calendar app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calendar">Calendar</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the calculator app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_calculator">Calculator</string>
+ <!-- User visible title for the keyboard shortcut that takes the user to the maps app. [CHAR LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications_maps">Maps</string>
+ <!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] -->
+ <string name="keyboard_shortcut_group_applications">Applications</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d25f59d..c50b961 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5561,4 +5561,15 @@
<java-symbol type="string" name="bg_user_sound_notification_button_switch_user" />
<java-symbol type="string" name="bg_user_sound_notification_button_mute" />
<java-symbol type="string" name="bg_user_sound_notification_message" />
+
+ <!-- Keyboard Shortcut default category names. -->
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_browser" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calculator" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_calendar" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_contacts" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_email" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_maps" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_music" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications_sms" />
+ <java-symbol type="string" name="keyboard_shortcut_group_applications" />
</resources>
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/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 037fbb2..1a9c304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -206,13 +206,12 @@
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
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/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..a749019 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,12 +16,10 @@
package com.android.wm.shell.pip;
-import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -71,10 +69,9 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
- * started / finished callbacks.
+ * @return {@link PipTransitionController} instance.
*/
- default void registerPipTransitionCallback(
- @NonNull PipTransitionController.PipTransitionCallback callback,
- @NonNull Executor executor) { }
+ default PipTransitionController getPipTransitionController() {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 6e1e44b..ff40d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,8 +423,7 @@
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(
- mPipTransitionCallback, mMainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
}
}
@@ -496,9 +495,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
- if (!ENABLE_SHELL_TRANSITIONS) {
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
- }
+ sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
return mPipBoundsAlgorithm.getEntryDestinationBounds();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 87692ac..e5633de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1174,7 +1174,6 @@
}
final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 8d36db9..b1dd4f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,9 +53,8 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
+import java.util.ArrayList;
+import java.util.List;
/**
* Responsible supplying PiP Transitions.
@@ -67,7 +66,7 @@
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
+ private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -184,18 +183,16 @@
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(
- @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
- mPipTransitionCallbacks.put(callback, executor);
+ public void registerPipTransitionCallback(PipTransitionCallback callback) {
+ mPipTransitionCallbacks.add(callback);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionStarted(direction, pipBounds);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -212,10 +209,9 @@
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionFinished(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionFinished(direction);
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -232,10 +228,9 @@
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (Map.Entry<PipTransitionCallback, Executor> entry
- : mPipTransitionCallbacks.entrySet()) {
- entry.getValue().execute(
- () -> entry.getKey().onPipTransitionCanceled(direction));
+ for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
+ final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
+ callback.onPipTransitionCanceled(direction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 0cb7e17..26b7e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,7 +106,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -479,7 +478,7 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1221,11 +1220,8 @@
}
@Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {
- mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
- callback, executor));
+ public PipTransitionController getPipTransitionController() {
+ return mPipTransitionController;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index df3803d..e8d6576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,7 +38,6 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -48,7 +47,6 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -173,9 +171,7 @@
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context,
- @ShellMainThread ShellExecutor mainExecutor,
- @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -187,7 +183,7 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 0ed5079..62c0944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
+ mPipTransitionController.registerPipTransitionCallback(this);
reloadResources();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index c12219c..b939b16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -64,7 +64,6 @@
import com.android.wm.shell.sysui.ShellInit;
import java.io.PrintWriter;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -413,11 +412,6 @@
@Override
public void showPictureInPictureMenu() {}
-
- @Override
- public void registerPipTransitionCallback(
- PipTransitionController.PipTransitionCallback callback,
- Executor executor) {}
}
/**
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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6888de5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -182,7 +182,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 66f8c0b..ace09a8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 6d18e36..92762fa 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
- mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
+ mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
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/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
index 7065e3a..46c7273 100644
--- a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -27,6 +27,7 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.lang.ref.WeakReference;
+import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -35,12 +36,13 @@
/** Utils for audio tests. */
public class TestUtils {
/**
- * Return a future for an intent delivered by a broadcast receiver which matches an
- * action and predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches an action and
+ * predicate.
+ *
* @param context - Context to register the receiver with
* @param action - String representing action to register receiver for
- * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
- * the future unset. If the predicate throws, the future is set exceptionally
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
* @return - The future representing intent delivery matching predicate.
*/
public static ListenableFuture<Intent> getFutureForIntent(
@@ -76,20 +78,77 @@
}
/**
- * Same as previous, but with no predicate.
+ * Return a future for an intent delivered by a broadcast receiver which matches one of a set of
+ * actions and predicate.
+ *
+ * @param context - Context to register the receiver with
+ * @param actionsCollection - Collection of actions which to listen for, completing on any
+ * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
+ * future unset. If the predicate throws, the future is set exceptionally
+ * @return - The future representing intent delivery matching predicate.
*/
+ public static ListenableFuture<Intent> getFutureForIntent(
+ Context context, Collection<String> actionsCollection, Predicate<Intent> pred) {
+ // These are evaluated async
+ Objects.requireNonNull(actionsCollection);
+ Objects.requireNonNull(pred);
+ if (actionsCollection.isEmpty()) {
+ throw new IllegalArgumentException("actionsCollection must not be empty");
+ }
+ return getFutureForListener(
+ (recv) ->
+ context.registerReceiver(
+ recv,
+ actionsCollection.stream()
+ .reduce(
+ new IntentFilter(),
+ (IntentFilter filter, String x) -> {
+ filter.addAction(x);
+ return filter;
+ },
+ (x, y) -> {
+ throw new IllegalStateException(
+ "No parallel support");
+ }),
+ Context.RECEIVER_EXPORTED),
+ (recv) -> {
+ try {
+ context.unregisterReceiver(recv);
+ } catch (IllegalArgumentException e) {
+ // Thrown when receiver is already unregistered, nothing to do
+ }
+ },
+ (completer) ->
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ if (actionsCollection.contains(intent.getAction())
+ && pred.test(intent)) {
+ completer.set(intent);
+ }
+ } catch (Exception e) {
+ completer.setException(e);
+ }
+ }
+ },
+ "Intent receiver future for actions: " + actionsCollection);
+ }
+
+ /** Same as previous, but with no predicate. */
public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
return getFutureForIntent(context, action, i -> true);
}
/**
* Return a future for a callback registered to a listener interface.
+ *
* @param registerFunc - Function which consumes the callback object for registration
- * @param unregisterFunc - Function which consumes the callback object for unregistration
- * This function is called when the future is completed or cancelled
+ * @param unregisterFunc - Function which consumes the callback object for unregistration This
+ * function is called when the future is completed or cancelled
* @param instantiateCallback - Factory function for the callback object, provided a completer
- * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
- * to the future returned by this function
+ * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
+ * to the future returned by this function
* @param debug - Debug string contained in future {@code toString} representation.
*/
public static <T, V> ListenableFuture<T> getFutureForListener(
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/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
index b949cd5..ac0b9b4 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/TileUtils.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.drawer;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -25,7 +26,9 @@
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -36,6 +39,8 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.util.ArrayList;
@@ -102,6 +107,9 @@
/** The key used to get the package name of the icon resource for the preference. */
static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
+ /** The key used for the raw byte data of the icon for the preference. */
+ static final String EXTRA_PREFERENCE_ICON_RAW = "com.android.settings.icon_raw";
+
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the key that should be used for the preference.
@@ -518,6 +526,24 @@
}
/**
+ * Retrieves an icon stored in the Bundle as a Parcel with key EXTRA_PREFERENCE_ICON_RAW
+ */
+ @TargetApi(Build.VERSION_CODES.TIRAMISU)
+ @Nullable
+ public static Icon getRawIconFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return null;
+ }
+ final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
+ if (bundle == null) {
+ return null;
+ }
+
+ return bundle.getParcelable(EXTRA_PREFERENCE_ICON_RAW, Icon.class);
+ }
+
+ /**
* Gets text associated with the input key from the content provider.
*
* @param context context
@@ -564,8 +590,9 @@
return getBundleFromUri(context, uri, providerMap, bundle);
}
- private static Bundle getBundleFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap, Bundle bundle) {
+ @Nullable
+ private static Bundle getBundleFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap, @Nullable Bundle bundle) {
final Pair<String, String> args = getMethodAndKey(uri);
if (args == null) {
return null;
@@ -593,8 +620,9 @@
}
}
- private static IContentProvider getProviderFromUri(Context context, Uri uri,
- Map<String, IContentProvider> providerMap) {
+ @Nullable
+ private static IContentProvider getProviderFromUri(@NonNull Context context, @Nullable Uri uri,
+ @NonNull Map<String, IContentProvider> providerMap) {
if (uri == null) {
return null;
}
@@ -609,7 +637,8 @@
}
/** Returns method and key of the complete uri. */
- private static Pair<String, String> getMethodAndKey(Uri uri) {
+ @Nullable
+ private static Pair<String, String> getMethodAndKey(@Nullable Uri uri) {
if (uri == null) {
return null;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 7f6a8ed..7886e85 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -18,6 +18,9 @@
import android.app.NotificationManager
import android.provider.Settings
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -32,6 +35,11 @@
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ MutableStateFlow(listOf(TestModeBuilder.EXAMPLE))
+ override val modes: Flow<List<ZenMode>>
+ get() = mutableModesFlow.asStateFlow()
+
init {
updateNotificationPolicy()
}
@@ -43,6 +51,20 @@
fun updateZenMode(zenMode: Int) {
mutableZenMode.value = zenMode
}
+
+ fun addMode(id: String, active: Boolean = false) {
+ mutableModesFlow.value += newMode(id, active)
+ }
+
+ fun removeMode(id: String) {
+ mutableModesFlow.value = mutableModesFlow.value.filter { it.id != id }
+ }
+
+ fun deactivateMode(id: String) {
+ val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
+ removeMode(id)
+ mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ }
}
fun FakeZenModeRepository.updateNotificationPolicy(
@@ -61,5 +83,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- )
- )
+ ))
+
+private fun newMode(id: String, active: Boolean = false): ZenMode {
+ return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 72c3c17..ef94526 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -18,18 +18,26 @@
import android.app.NotificationManager
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -44,11 +52,16 @@
/** @see NotificationManager.getZenMode */
val globalZenMode: StateFlow<Int?>
+
+ /** A list of all existing priority modes. */
+ val modes: Flow<List<ZenMode>>
}
class ZenModeRepositoryImpl(
private val context: Context,
private val notificationManager: NotificationManager,
+ private val backend: ZenModesBackend,
+ private val contentResolver: ContentResolver,
val scope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
@@ -87,7 +100,6 @@
.let {
if (Flags.volumePanelBroadcastFix()) {
it.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
} else {
it.shareIn(
started = SharingStarted.WhileSubscribed(),
@@ -121,4 +133,45 @@
.onStart { emit(mapper()) }
.flowOn(backgroundCoroutineContext)
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ private val zenConfigChanged by lazy {
+ if (android.app.Flags.modesUi()) {
+ callbackFlow {
+ // emit an initial value
+ trySend(Unit)
+
+ val observer =
+ object : ContentObserver(backgroundHandler) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ /* notifyForDescendants= */ false,
+ observer)
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG),
+ /* notifyForDescendants= */ false,
+ observer)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(Unit)
+ }
+ }
+
+ override val modes: Flow<List<ZenMode>> by lazy {
+ if (android.app.Flags.modesUi()) {
+ zenConfigChanged
+ .map { backend.modes }
+ .distinctUntilChanged()
+ .flowOn(backgroundCoroutineContext)
+ } else {
+ flowOf(emptyList())
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
new file mode 100644
index 0000000..7b994d5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+
+import java.util.Random;
+
+public class TestModeBuilder {
+
+ private String mId;
+ private AutomaticZenRule mRule;
+ private ZenModeConfig.ZenRule mConfigZenRule;
+
+ public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+
+ public TestModeBuilder() {
+ // Reasonable defaults
+ int id = new Random().nextInt(1000);
+ mId = "rule_" + id;
+ mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
+ .setPackage("some_package")
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build();
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.pkg = "some_package";
+ }
+
+ public TestModeBuilder(ZenMode previous) {
+ mId = previous.getId();
+ mRule = previous.getRule();
+
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = previous.getRule().isEnabled();
+ mConfigZenRule.pkg = previous.getRule().getPackageName();
+ setActive(previous.isActive());
+ }
+
+ public TestModeBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public TestModeBuilder setAzr(AutomaticZenRule rule) {
+ mRule = rule;
+ mConfigZenRule.pkg = rule.getPackageName();
+ mConfigZenRule.conditionId = rule.getConditionId();
+ mConfigZenRule.enabled = rule.isEnabled();
+ return this;
+ }
+
+ public TestModeBuilder setConfigZenRule(ZenModeConfig.ZenRule configZenRule) {
+ mConfigZenRule = configZenRule;
+ return this;
+ }
+
+ public TestModeBuilder setName(String name) {
+ mRule.setName(name);
+ mConfigZenRule.name = name;
+ return this;
+ }
+
+ public TestModeBuilder setPackage(String pkg) {
+ mRule.setPackageName(pkg);
+ mConfigZenRule.pkg = pkg;
+ return this;
+ }
+
+ public TestModeBuilder setOwner(ComponentName owner) {
+ mRule.setOwner(owner);
+ mConfigZenRule.component = owner;
+ return this;
+ }
+
+ public TestModeBuilder setConfigurationActivity(ComponentName configActivity) {
+ mRule.setConfigurationActivity(configActivity);
+ mConfigZenRule.configurationActivity = configActivity;
+ return this;
+ }
+
+ public TestModeBuilder setConditionId(Uri conditionId) {
+ mRule.setConditionId(conditionId);
+ mConfigZenRule.conditionId = conditionId;
+ return this;
+ }
+
+ public TestModeBuilder setType(@AutomaticZenRule.Type int type) {
+ mRule.setType(type);
+ mConfigZenRule.type = type;
+ return this;
+ }
+
+ public TestModeBuilder setInterruptionFilter(
+ @NotificationManager.InterruptionFilter int interruptionFilter) {
+ mRule.setInterruptionFilter(interruptionFilter);
+ mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ return this;
+ }
+
+ public TestModeBuilder setZenPolicy(@Nullable ZenPolicy policy) {
+ mRule.setZenPolicy(policy);
+ mConfigZenRule.zenPolicy = policy;
+ return this;
+ }
+
+ public TestModeBuilder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mRule.setDeviceEffects(deviceEffects);
+ mConfigZenRule.zenDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled) {
+ mRule.setEnabled(enabled);
+ mConfigZenRule.enabled = enabled;
+ return this;
+ }
+
+ public TestModeBuilder setManualInvocationAllowed(boolean allowed) {
+ mRule.setManualInvocationAllowed(allowed);
+ mConfigZenRule.allowManualInvocation = allowed;
+ return this;
+ }
+
+ public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ mRule.setTriggerDescription(triggerDescription);
+ mConfigZenRule.triggerDescription = triggerDescription;
+ return this;
+ }
+
+ public TestModeBuilder setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ return this;
+ }
+
+ public TestModeBuilder setActive(boolean active) {
+ if (active) {
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.condition = new Condition(mRule.getConditionId(), "...",
+ Condition.STATE_TRUE);
+ } else {
+ mConfigZenRule.condition = null;
+ }
+ return this;
+ }
+
+ public ZenMode build() {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 06333b61..6e11e1f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -18,13 +18,18 @@
import android.app.NotificationManager
import android.content.BroadcastReceiver
+import android.content.ContentResolver
import android.content.Context
import android.content.Intent
+import android.database.ContentObserver
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Global
import androidx.test.filters.SmallTest
import com.android.settingslib.flags.Flags
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModesBackend
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -36,6 +41,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.any
@@ -52,8 +58,16 @@
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var zenModesBackend: ZenModesBackend
+
+ @Mock private lateinit var contentResolver: ContentResolver
+
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+ @Captor private lateinit var zenModeObserverCaptor: ArgumentCaptor<ContentObserver>
+
+ @Captor private lateinit var zenConfigObserverCaptor: ArgumentCaptor<ContentObserver>
+
private lateinit var underTest: ZenModeRepository
private val testScope: TestScope = TestScope()
@@ -66,6 +80,8 @@
ZenModeRepositoryImpl(
context,
notificationManager,
+ zenModesBackend,
+ contentResolver,
testScope.backgroundScope,
testScope.testScheduler,
backgroundHandler = null,
@@ -128,11 +144,61 @@
}
}
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ @Test
+ fun modesListEmitsOnSettingsChange() {
+ testScope.runTest {
+ val values = mutableListOf<List<ZenMode>>()
+ val modes1 = listOf(TestModeBuilder().setId("One").build())
+ `when`(zenModesBackend.modes).thenReturn(modes1)
+ underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ // zen mode change triggers update
+ val modes2 = listOf(TestModeBuilder().setId("Two").build())
+ `when`(zenModesBackend.modes).thenReturn(modes2)
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ // zen config change also triggers update
+ val modes3 = listOf(TestModeBuilder().setId("Three").build())
+ `when`(zenModesBackend.modes).thenReturn(modes3)
+ triggerZenConfigSettingUpdate()
+ runCurrent()
+
+ // setting update with no list change doesn't trigger update
+ triggerZenModeSettingUpdate()
+ runCurrent()
+
+ assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ }
+ }
+
private fun triggerIntent(action: String) {
verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
receiverCaptor.value.onReceive(context, Intent(action))
}
+ private fun triggerZenModeSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE)),
+ eq(false),
+ zenModeObserverCaptor.capture(),
+ )
+ zenModeObserverCaptor.value.onChange(false)
+ }
+
+ private fun triggerZenConfigSettingUpdate() {
+ verify(contentResolver)
+ .registerContentObserver(
+ eq(Global.getUriFor(Global.ZEN_MODE_CONFIG_ETAG)),
+ eq(false),
+ zenConfigObserverCaptor.capture(),
+ )
+ zenConfigObserverCaptor.value.onChange(false)
+ }
+
private companion object {
val testPolicy1 =
NotificationManager.Policy(
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/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12ca997..a184cf3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -337,7 +337,7 @@
// expanded, reset scrim offset.
LaunchedEffect(stackHeight, scrimOffset) {
snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f }
- .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) }
+ .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.animateTo(0f, tween()) }
}
// if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly.
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 51c008a..9b725eb 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -17,7 +17,6 @@
import android.app.UserSwitchObserver
import android.content.Context
import android.database.ContentObserver
-import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.UserHandle
import android.provider.Settings
@@ -33,6 +32,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
@@ -341,6 +341,7 @@
}
private var isClockChanged = AtomicBoolean(false)
+
private fun triggerOnCurrentClockChanged() {
val shouldSchedule = isClockChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -355,6 +356,7 @@
}
private var isClockListChanged = AtomicBoolean(false)
+
private fun triggerOnAvailableClocksChanged() {
val shouldSchedule = isClockListChanged.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -458,6 +460,7 @@
}
private var isQueued = AtomicBoolean(false)
+
fun verifyLoadedProviders() {
val shouldSchedule = isQueued.compareAndSet(false, true)
if (!shouldSchedule) {
@@ -565,8 +568,8 @@
return availableClocks.map { (_, clock) -> clock.metadata }
}
- fun getClockThumbnail(clockId: ClockId): Drawable? =
- availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+ fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
+ availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 9e0af97..4802e34 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -15,13 +15,13 @@
import android.content.Context
import android.content.res.Resources
-import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import com.android.systemui.customization.R
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProvider
import com.android.systemui.plugins.clocks.ClockSettings
@@ -60,12 +60,17 @@
)
}
- override fun getClockThumbnail(id: ClockId): Drawable? {
+ override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
if (id != DEFAULT_CLOCK_ID) {
throw IllegalArgumentException("$id is unsupported by $TAG")
}
- // TODO(b/352049256): Update placeholder to actual resource
- return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ return ClockPickerConfig(
+ DEFAULT_CLOCK_ID,
+ resources.getString(R.string.clock_default_name),
+ resources.getString(R.string.clock_default_description),
+ // TODO(b/352049256): Update placeholder to actual resource
+ resources.getDrawable(R.drawable.clock_default_thumbnail, null),
+ )
}
}
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..09dca25 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,10 +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
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.notification.data.repository.FakeZenModeRepository
@@ -42,7 +38,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ModesTileDataInteractorTest : SysuiTestCase() {
private val zenModeRepository = FakeZenModeRepository()
@@ -67,24 +62,28 @@
@EnableFlags(Flags.FLAG_MODES_UI)
@Test
- fun dataMatchesTheRepository() = runTest {
+ fun isActivatedWhenModesChange() = runTest {
val dataList: List<ModesTileModel> by
collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
- // Enable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Change zen mode: it's still enabled, so this shouldn't cause another emission
- zenModeRepository.updateZenMode(ZEN_MODE_NO_INTERRUPTIONS)
+ // Add another mode: state hasn't changed, so this shouldn't cause another emission
+ zenModeRepository.addMode(id = "Two", active = true)
runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
- // Disable zen mode
- zenModeRepository.updateZenMode(ZEN_MODE_OFF)
+ // Remove a mode and disable the other
+ zenModeRepository.removeMode("One")
runCurrent()
-
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false)
+ zenModeRepository.deactivateMode("Two")
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false).inOrder()
}
private companion object {
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/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index ba7ddce..f8e6337 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -633,7 +633,7 @@
@Test
@EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
- fun headsUpAnimationsEnabled_keyguardShowing_false() =
+ fun headsUpAnimationsEnabled_keyguardShowing_true() =
testScope.runTest {
val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
@@ -641,6 +641,6 @@
fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
runCurrent()
- assertThat(animationsEnabled).isFalse()
+ assertThat(animationsEnabled).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 663cf1c..d0ddbff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -22,11 +22,15 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -34,13 +38,17 @@
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@@ -51,6 +59,7 @@
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
@@ -58,6 +67,9 @@
private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
@Mock private lateinit var mGroupManager: GroupMembershipManager
@Mock private lateinit var mVSProvider: VisualStabilityProvider
@@ -72,7 +84,7 @@
@Mock private lateinit var mUiEventLogger: UiEventLogger
- @Mock private lateinit var mJavaAdapter: JavaAdapter
+ private val mJavaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
@Mock private lateinit var mShadeInteractor: ShadeInteractor
@@ -120,6 +132,11 @@
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
mAutoDismissTime = TEST_AUTO_DISMISS_TIME
}
+
+ /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
+ fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
+ return shouldHeadsUpBecomePinned(entry)
+ }
}
private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
@@ -219,6 +236,196 @@
Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
}
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeLocked_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(1207)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(true)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mBypassController.bypassEnabled).thenReturn(false)
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
+ @Test
+ fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
+ testScope.runTest {
+ // GIVEN
+ val statusBarStateController = FakeStatusBarStateController()
+ whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
+ val hmp =
+ TestableHeadsUpManagerPhone(
+ mContext,
+ mHeadsUpManagerLogger,
+ mGroupManager,
+ mVSProvider,
+ statusBarStateController,
+ mBypassController,
+ mConfigurationController,
+ mGlobalSettings,
+ mSystemClock,
+ mExecutor,
+ mAccessibilityManagerWrapper,
+ mUiEventLogger,
+ mJavaAdapter,
+ mShadeInteractor,
+ mAvalancheController
+ )
+ val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+ statusBarStateController.setState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN
+ Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+ }
+
companion object {
@get:Parameters(name = "{0}")
val flags: List<FlagsParameterization>
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index c7998f0..4812ff0 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -50,8 +50,8 @@
/** Initializes and returns the target clock design */
fun createClock(settings: ClockSettings): ClockController
- /** A static thumbnail for rendering in some examples */
- fun getClockThumbnail(id: ClockId): Drawable?
+ /** Settings configuration parameters for the clock */
+ fun getClockPickerConfig(id: ClockId): ClockPickerConfig
}
/** Interface for controlling an active clock */
@@ -133,6 +133,7 @@
// both small and large clock should have a container (RelativeLayout in
// SimpleClockFaceController)
override val views = listOf(view)
+
override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
if (views.size != 1) {
throw IllegalArgumentException(
@@ -267,6 +268,25 @@
val clockId: ClockId,
)
+data class ClockPickerConfig(
+ val id: String,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /* Static & lightweight thumbnail version of the clock */
+ val thumbnail: Drawable,
+
+ /** True if the clock will react to tone changes in the seed color */
+ val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is capable of chagning style in reaction to touches */
+ val isReactiveToTouch: Boolean = false,
+)
+
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
val id: String,
@@ -280,7 +300,7 @@
/** Transition to AOD should move smartspace like large clock instead of small clock */
val useAlternateSmartspaceAODTransition: Boolean = false,
- /** True if the clock will react to tone changes in the seed color. */
+ @Deprecated("TODO(b/352049256): Remove")
val isReactiveToTone: Boolean = true,
/** True if the clock is large frame clock, which will use weather in compose. */
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52e5dea..2bd97d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1873,7 +1873,7 @@
<string name="notification_channel_summary_low">No sound or vibration</string>
<!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
- <string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
+ <string name="notification_conversation_summary_low">No sound or vibration but still appears in the conversation section</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
<string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4ef1f93..484e758 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,8 +124,6 @@
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
- // PiP animation is running
- public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Communal hub is showing
public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
@@ -177,7 +175,6 @@
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
- SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -283,9 +280,6 @@
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
- if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
- str.add("disable_gesture_pip_animating");
- }
if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
str.add("communal_hub_showing");
}
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/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 9e9368d..b482862 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -120,6 +120,7 @@
ShortcutHelperSinglePane(
modifier,
shortcutsUiState.shortcutCategories,
+ shortcutsUiState.defaultSelectedCategory,
onKeyboardSettingsClicked
)
} else {
@@ -146,6 +147,7 @@
private fun ShortcutHelperSinglePane(
modifier: Modifier = Modifier,
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
onKeyboardSettingsClicked: () -> Unit,
) {
Column(
@@ -159,7 +161,7 @@
Spacer(modifier = Modifier.height(6.dp))
ShortcutsSearchBar()
Spacer(modifier = Modifier.height(16.dp))
- CategoriesPanelSinglePane(categories)
+ CategoriesPanelSinglePane(categories, defaultSelectedCategory)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onClick = onKeyboardSettingsClicked)
}
@@ -168,8 +170,10 @@
@Composable
private fun CategoriesPanelSinglePane(
categories: List<ShortcutCategory>,
+ defaultSelectedCategory: ShortcutCategoryType,
) {
- var expandedCategory by remember { mutableStateOf<ShortcutCategory?>(null) }
+ val selectedCategory = categories.firstOrNull { it.type == defaultSelectedCategory }
+ var expandedCategory by remember { mutableStateOf(selectedCategory) }
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
categories.fastForEachIndexed { index, category ->
val isExpanded = expandedCategory == category
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..25574ea 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,9 @@
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.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -40,8 +42,8 @@
) {
val shouldShow =
- stateInteractor.state
- .map { it is ShortcutHelperState.Active }
+ categoriesInteractor.shortcutCategories
+ .map { it.isNotEmpty() }
.distinctUntilChanged()
.flowOn(backgroundDispatcher)
@@ -53,7 +55,7 @@
} else {
ShortcutsUiState.Active(
shortcutCategories = it,
- defaultSelectedCategory = it.first().type,
+ defaultSelectedCategory = getDefaultSelectedCategory(it),
)
}
}
@@ -63,6 +65,13 @@
initialValue = ShortcutsUiState.Inactive
)
+ private fun getDefaultSelectedCategory(
+ categories: List<ShortcutCategory>
+ ): ShortcutCategoryType {
+ val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ return currentAppShortcuts?.type ?: categories.first().type
+ }
+
fun onViewClosed() {
stateInteractor.onViewClosed()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fe81b20c..e46a7cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -78,7 +78,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
@@ -151,6 +150,7 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
@@ -360,6 +360,7 @@
private final SecureSettings mSecureSettings;
private final SystemSettings mSystemSettings;
private final SystemClock mSystemClock;
+ private final ProcessWrapper mProcessWrapper;
private final SystemPropertiesHelper mSystemPropertiesHelper;
/**
@@ -1459,10 +1460,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -1487,9 +1490,9 @@
mSecureSettings = secureSettings;
mSystemSettings = systemSettings;
mSystemClock = systemClock;
+ mProcessWrapper = processWrapper;
mSystemPropertiesHelper = systemPropertiesHelper;
- mStatusBarService = IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE));
+ mStatusBarService = statusBarService;
mKeyguardDisplayManager = keyguardDisplayManager;
mShadeController = shadeControllerLazy;
dumpManager.registerDumpable(this);
@@ -2834,6 +2837,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 +2896,6 @@
mKeyguardDisplayManager.show();
scheduleNonStrongBiometricIdleTimeout();
-
- Trace.endSection();
}
/**
@@ -3065,6 +3074,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 +3273,6 @@
onKeyguardExitFinished();
}
}
-
- Trace.endSection();
}
private void onKeyguardExitFinished() {
@@ -3496,12 +3514,20 @@
// TODO (b/155663717) After restart, status bar will not properly hide home button
// unless disable is called to show un-hide it once first
if (forceClearFlags) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ } else {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to force clear flags", e);
+ }
}
}
@@ -3525,6 +3551,14 @@
}
if (!SceneContainerFlag.isEnabled()) {
+ if (UserManager.isVisibleBackgroundUsersEnabled()
+ && !mProcessWrapper.isSystemUser() && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
try {
mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
mContext.getPackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 15dac09..a43bfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -23,6 +23,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -64,6 +65,7 @@
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -158,10 +160,12 @@
Lazy<ActivityTransitionAnimator> activityTransitionAnimator,
Lazy<ScrimController> scrimControllerLazy,
IActivityTaskManager activityTaskManagerService,
+ IStatusBarService statusBarService,
FeatureFlags featureFlags,
SecureSettings secureSettings,
SystemSettings systemSettings,
SystemClock systemClock,
+ ProcessWrapper processWrapper,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamViewModel> dreamViewModel,
Lazy<CommunalTransitionViewModel> communalTransitionViewModel,
@@ -206,10 +210,12 @@
activityTransitionAnimator,
scrimControllerLazy,
activityTaskManagerService,
+ statusBarService,
featureFlags,
secureSettings,
systemSettings,
systemClock,
+ processWrapper,
mainDispatcher,
dreamViewModel,
communalTransitionViewModel,
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/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index d848b43..e8ded03 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.os.RemoteException;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -36,6 +37,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
@@ -63,6 +65,7 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
+ private final ProcessWrapper mProcessWrapper;
private final Map<Integer, InstanceId> mSessionToInstanceId = new HashMap<>();
private boolean mKeyguardSessionStarted;
@@ -73,13 +76,15 @@
AuthController authController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardStateController keyguardStateController,
- UiEventLogger uiEventLogger
+ UiEventLogger uiEventLogger,
+ ProcessWrapper processWrapper
) {
mStatusBarManagerService = statusBarService;
mAuthController = authController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mKeyguardStateController = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mProcessWrapper = processWrapper;
}
@Override
@@ -109,6 +114,16 @@
final InstanceId instanceId = mInstanceIdGenerator.newInstanceId();
mSessionToInstanceId.put(type, instanceId);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
+
try {
if (DEBUG) {
Log.d(TAG, "Session start for [" + getString(type) + "] id=" + instanceId);
@@ -139,6 +154,14 @@
if (endSessionUiEvent != null) {
mUiEventLogger.log(endSessionUiEvent, instanceId);
}
+ if (UserManager.isVisibleBackgroundUsersEnabled() && !mProcessWrapper.isSystemUser()
+ && !mProcessWrapper.isForegroundUser()) {
+ // TODO: b/341604160 - Support visible background users properly.
+ if (DEBUG) {
+ Log.d(TAG, "Status bar manager is disabled for visible background users");
+ }
+ return;
+ }
mStatusBarManagerService.onSessionEnded(type, instanceId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to send onSessionEnded for session="
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index bda0069..e7c2a45 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,7 +17,6 @@
package com.android.systemui.media;
import android.annotation.Nullable;
-import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -124,13 +123,9 @@
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
- Log.d(TAG, "play(token=" + token + ", uri=" + uri
- + ", uid=" + Binder.getCallingUid()
- + ") uriUserId=" + ContentProvider.getUserIdFromUri(uri)
- + " callingUserId=" + Binder.getCallingUserHandle().getIdentifier());
+ Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ + Binder.getCallingUid() + ")");
}
- enforceUriUserId(uri);
-
Client client;
synchronized (mClients) {
client = mClients.get(token);
@@ -212,7 +207,6 @@
@Override
public String getTitle(Uri uri) {
- enforceUriUserId(uri);
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false /*followSettingsUri*/, false /*allowRemote*/);
@@ -245,25 +239,6 @@
}
throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
}
-
- /**
- * Must be called from the Binder calling thread.
- * Ensures caller is from the same userId as the content they're trying to access.
- * @param uri the URI to check
- * @throws SecurityException when non-system call or userId in uri differs from the
- * caller's userId
- */
- private void enforceUriUserId(Uri uri) throws SecurityException {
- final int uriUserId = ContentProvider.getUserIdFromUri(uri);
- final int callerUserId = Binder.getCallingUserHandle().getIdentifier();
- // for a non-system call, verify the URI to play belongs to the same user as the caller
- if (UserHandle.isApp(Binder.getCallingUid()) && uriUserId != callerUserId) {
- throw new SecurityException("Illegal access to uri=" + uri
- + " content associated with user=" + uriUserId
- + ", request originates from user=" + callerUserId);
- }
- }
-
};
private Context getContextForUser(UserHandle user) {
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..c90f197 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(
@@ -67,7 +69,11 @@
}
override suspend fun stopProjecting() {
- withContext(backgroundDispatcher) { mediaProjectionManager.stopActiveProjection() }
+ withContext(backgroundDispatcher) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
+ Log.d(TAG, "Requesting MediaProjectionManager#stopActiveProjection")
+ mediaProjectionManager.stopActiveProjection()
+ }
}
override val mediaProjectionState: Flow<MediaProjectionState> =
@@ -76,12 +82,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 +95,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 +157,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/mediarouter/MediaRouterLog.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
new file mode 100644
index 0000000..16bf0ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.mediarouter
+
+import javax.inject.Qualifier
+
+/** Logs for events related to MediaRouter APIs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class MediaRouterLog
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
index c07e3a0..df5dae4 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/MediaRouterModule.kt
@@ -16,12 +16,25 @@
package com.android.systemui.mediarouter
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
import com.android.systemui.mediarouter.data.repository.MediaRouterRepositoryImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface MediaRouterModule {
@Binds fun mediaRouterRepository(impl: MediaRouterRepositoryImpl): MediaRouterRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @MediaRouterLog
+ fun provideMediaRouterLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MediaRouter", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
index 998d76c..debb667 100644
--- a/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepository.kt
@@ -18,6 +18,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.mediarouter.MediaRouterLog
import com.android.systemui.statusbar.policy.CastController
import com.android.systemui.statusbar.policy.CastDevice
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -26,6 +29,9 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** A repository for data coming from MediaRouter APIs. */
@@ -43,23 +49,29 @@
constructor(
@Application private val scope: CoroutineScope,
private val castController: CastController,
+ @MediaRouterLog private val logger: LogBuffer,
) : MediaRouterRepository {
override val castDevices: StateFlow<List<CastDevice>> =
conflatedCallbackFlow {
- val callback =
- CastController.Callback {
- val mediaRouterCastDevices =
- castController.castDevices.filter {
- it.origin == CastDevice.CastOrigin.MediaRouter
- }
- trySend(mediaRouterCastDevices)
- }
+ val callback = CastController.Callback { trySend(castController.castDevices) }
castController.addCallback(callback)
awaitClose { castController.removeCallback(callback) }
}
+ // The CastController.Callback is pretty noisy and sends the same values multiple times
+ // in a row, so use a distinctUntilChanged before logging.
+ .distinctUntilChanged()
+ .onEach { allDevices ->
+ val logString = allDevices.map { it.shortLogString }.toString()
+ logger.log(TAG, LogLevel.INFO, { str1 = logString }, { "All cast devices: $str1" })
+ }
+ .map { it.filter { device -> device.origin == CastDevice.CastOrigin.MediaRouter } }
.stateIn(scope, SharingStarted.WhileSubscribed(), emptyList())
override fun stopCasting(device: CastDevice) {
castController.stopCasting(device)
}
+
+ companion object {
+ private const val TAG = "MediaRouterRepo"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index b4cc196..294d0c7 100644
--- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.process;
+import android.app.ActivityManager;
import android.os.Process;
import android.os.UserHandle;
@@ -37,6 +38,13 @@
}
/**
+ * Returns {@code true} if the foreground user is running the current process.
+ */
+ public boolean isForegroundUser() {
+ return ActivityManager.getCurrentUser() == myUserHandle().getIdentifier();
+ }
+
+ /**
* Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}.
*
* This should not be used to get the "current" user. This information only applies to the
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 787fd1a..6b3dfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -93,6 +93,7 @@
@VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_"
@VisibleForTesting internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
@VisibleForTesting internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
+ internal val EMPTY_RECT = Rect()
}
private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -916,7 +917,7 @@
}
}
- fun prepareForLaunch() {
+ private fun prepareForLaunch() {
val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
val deltaH = finalLongPressProperties?.height?.minus(startingHeight)?.toInt() ?: 0
@@ -927,7 +928,12 @@
paddingForLaunch.bottom = deltaH / 2
}
- override fun getPaddingForLaunchAnimation(): Rect = paddingForLaunch
+ override fun getPaddingForLaunchAnimation(): Rect =
+ if (longPressEffect?.state == QSLongPressEffect.State.LONG_CLICKED) {
+ paddingForLaunch
+ } else {
+ EMPTY_RECT
+ }
fun updateLongPressEffectProperties(effectProgress: Float) {
if (!isLongClickable || longPressEffect == null) return
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/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index da4d2f1..930109a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -18,7 +18,6 @@
import android.app.Flags
import android.os.UserHandle
-import android.provider.Settings.Global.ZEN_MODE_OFF
import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -31,9 +30,10 @@
class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
QSTileDataInteractor<ModesTileModel> {
- // TODO(b/346519570): This should be checking for any enabled modes.
private val zenModeActive =
- zenModeRepository.globalZenMode.map { it != ZEN_MODE_OFF }.distinctUntilChanged()
+ zenModeRepository.modes
+ .map { modes -> modes.any { mode -> mode.isActive } }
+ .distinctUntilChanged()
override fun tileData(
user: UserHandle,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 3dc2070..af1b6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -258,6 +258,7 @@
* Stop the recording
*/
public void stopRecording() {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
try {
if (mStopIntent != null) {
mStopIntent.send(mInteractiveBroadcastOption);
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/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 5bb2936..c997ac5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -19,6 +19,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -149,7 +150,7 @@
private KeyCharacterMap mBackupKeyCharacterMap;
@VisibleForTesting
- KeyboardShortcutListSearch(Context context, WindowManager windowManager) {
+ KeyboardShortcutListSearch(Context context, WindowManager windowManager, int deviceId) {
this.mContext = new ContextThemeWrapper(
context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
@@ -159,12 +160,12 @@
this.mWindowManager = mContext.getSystemService(WindowManager.class);
}
loadResources(this.mContext);
- createHardcodedShortcuts();
+ createHardcodedShortcuts(deviceId);
}
- private static KeyboardShortcutListSearch getInstance(Context context) {
+ private static KeyboardShortcutListSearch getInstance(Context context, int deviceId) {
if (sInstance == null) {
- sInstance = new KeyboardShortcutListSearch(context, null);
+ sInstance = new KeyboardShortcutListSearch(context, null, deviceId);
}
return sInstance;
}
@@ -176,7 +177,7 @@
if (sInstance != null && !sInstance.mContext.equals(context)) {
dismiss();
}
- getInstance(context).showKeyboardShortcuts(deviceId);
+ getInstance(context, deviceId).showKeyboardShortcuts(deviceId);
}
}
@@ -367,7 +368,7 @@
KeyEvent.META_META_ON, context.getDrawable(R.drawable.ic_ksh_key_meta));
}
- private void createHardcodedShortcuts() {
+ private void createHardcodedShortcuts(int deviceId) {
// Add system shortcuts
mKeySearchResultMap.put(SHORTCUT_SYSTEM_INDEX, true);
mSystemGroup.add(getMultiMappingSystemShortcuts(mContext));
@@ -377,7 +378,7 @@
mInputGroup.add(getMultiMappingInputShortcuts(mContext));
// Add open apps shortcuts
final List<KeyboardShortcutMultiMappingGroup> appShortcuts =
- Arrays.asList(getDefaultMultiMappingApplicationShortcuts());
+ Arrays.asList(getDefaultMultiMappingApplicationShortcuts(deviceId));
if (appShortcuts != null && !appShortcuts.isEmpty()) {
mOpenAppsGroup = appShortcuts;
mKeySearchResultMap.put(SHORTCUT_OPENAPPS_INDEX, true);
@@ -739,35 +740,50 @@
shortcutMultiMappingInfoList);
}
- private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts() {
- final int userId = mContext.getUserId();
- PackageInfo assistPackageInfo = getAssistPackageInfo(mContext, mPackageManager, userId);
- CharSequence categoryTitle =
- mContext.getString(R.string.keyboard_shortcut_group_applications);
+ private KeyboardShortcutMultiMappingGroup getDefaultMultiMappingApplicationShortcuts(
+ int deviceId) {
List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ List<KeyboardShortcutMultiMappingGroup> shortcuts =
+ reMapToKeyboardShortcutMultiMappingGroup(Arrays.asList(apps));
+ for (KeyboardShortcutMultiMappingGroup group : shortcuts) {
+ for (ShortcutMultiMappingInfo keyboardShortcutInfo : group.getItems()) {
+ shortcutMultiMappingInfos.add(keyboardShortcutInfo);
+ }
+ }
+ categoryTitle = apps.getLabel();
+ } else {
+ // Show shortcuts based on AOSP bookmarks.xml
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ final int userId = mContext.getUserId();
+ PackageInfo assistPackageInfo =
+ getAssistPackageInfo(mContext, mPackageManager, userId);
- String[] intentCategories = {
- Intent.CATEGORY_APP_BROWSER,
- Intent.CATEGORY_APP_CONTACTS,
- Intent.CATEGORY_APP_EMAIL,
- Intent.CATEGORY_APP_CALENDAR,
- Intent.CATEGORY_APP_MAPS,
- Intent.CATEGORY_APP_MUSIC,
- Intent.CATEGORY_APP_MESSAGING,
- Intent.CATEGORY_APP_CALCULATOR,
+ String[] intentCategories = {
+ Intent.CATEGORY_APP_BROWSER,
+ Intent.CATEGORY_APP_CONTACTS,
+ Intent.CATEGORY_APP_EMAIL,
+ Intent.CATEGORY_APP_CALENDAR,
+ Intent.CATEGORY_APP_MAPS,
+ Intent.CATEGORY_APP_MUSIC,
+ Intent.CATEGORY_APP_MESSAGING,
+ Intent.CATEGORY_APP_CALCULATOR,
+ };
+ String[] shortcutLabels = {
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
+ };
- };
- String[] shortcutLabels = {
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- mContext.getString(R.string.keyboard_shortcut_group_applications_calculator)
- };
- int[] keyCodes = {
+ int[] keyCodes = {
KeyEvent.KEYCODE_B,
KeyEvent.KEYCODE_C,
KeyEvent.KEYCODE_E,
@@ -776,52 +792,44 @@
KeyEvent.KEYCODE_P,
KeyEvent.KEYCODE_S,
KeyEvent.KEYCODE_U,
- };
+ };
- // Assist.
- if (assistPackageInfo != null) {
+ // Assist.
if (assistPackageInfo != null) {
- final Icon assistIcon = Icon.createWithResource(
- assistPackageInfo.applicationInfo.packageName,
- assistPackageInfo.applicationInfo.icon);
- CharSequence assistLabel =
- mContext.getString(R.string.keyboard_shortcut_group_applications_assist);
- KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
- assistLabel,
- assistIcon,
- KeyEvent.KEYCODE_A,
- KeyEvent.META_META_ON);
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(
- assistLabel,
- assistIcon,
- Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ if (assistPackageInfo != null) {
+ final Icon assistIcon = Icon.createWithResource(
+ assistPackageInfo.applicationInfo.packageName,
+ assistPackageInfo.applicationInfo.icon);
+ CharSequence assistLabel = mContext.getString(
+ R.string.keyboard_shortcut_group_applications_assist);
+ KeyboardShortcutInfo assistShortcutInfo = new KeyboardShortcutInfo(
+ assistLabel,
+ assistIcon,
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON);
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(
+ assistLabel,
+ assistIcon,
+ Arrays.asList(new ShortcutKeyGroup(assistShortcutInfo, null))));
+ }
}
- }
- // Browser (Chrome as default): Meta + B
- // Contacts: Meta + C
- // Email (Gmail as default): Meta + E
- // Gmail: Meta + G
- // Calendar: Meta + K
- // Maps: Meta + M
- // Music: Meta + P
- // SMS: Meta + S
- // Calculator: Meta + U
- for (int i = 0; i < shortcutLabels.length; i++) {
- final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
- if (icon != null) {
- CharSequence label =
- shortcutLabels[i];
- KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
- label,
- icon,
- keyCodes[i],
- KeyEvent.META_META_ON);
- List<ShortcutKeyGroup> shortcutKeyGroups =
- Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
- shortcutMultiMappingInfos.add(
- new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ for (int i = 0; i < shortcutLabels.length; i++) {
+ final Icon icon = getIconForIntentCategory(intentCategories[i], userId);
+ if (icon != null) {
+ CharSequence label =
+ shortcutLabels[i];
+ KeyboardShortcutInfo keyboardShortcutInfo = new KeyboardShortcutInfo(
+ label,
+ icon,
+ keyCodes[i],
+ KeyEvent.META_META_ON);
+ List<ShortcutKeyGroup> shortcutKeyGroups =
+ Arrays.asList(new ShortcutKeyGroup(keyboardShortcutInfo, null));
+ shortcutMultiMappingInfos.add(
+ new ShortcutMultiMappingInfo(label, icon, shortcutKeyGroups));
+ }
}
}
@@ -1221,7 +1229,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index a49ca38..da89eea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -20,6 +20,7 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static com.android.systemui.Flags.fetchBookmarksXmlKeyboardShortcuts;
import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri;
import android.annotation.NonNull;
@@ -75,6 +76,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
/**
* Contains functionality for handling keyboard shortcuts.
@@ -133,6 +135,7 @@
@Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
@Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+ @Nullable private KeyboardShortcutGroup mDefaultApplicationShortcuts = null;
@VisibleForTesting
KeyboardShortcuts(Context context, WindowManager windowManager) {
@@ -390,6 +393,7 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
+ mDefaultApplicationShortcuts = getDefaultApplicationShortcuts(deviceId);
mWindowManager.requestAppKeyboardShortcuts(
result -> {
mBackgroundHandler.post(() -> {
@@ -443,10 +447,8 @@
mReceivedAppShortcutGroups = null;
mReceivedImeShortcutGroups = null;
- final KeyboardShortcutGroup defaultAppShortcuts =
- getDefaultApplicationShortcuts();
- if (defaultAppShortcuts != null) {
- shortcutGroups.add(defaultAppShortcuts);
+ if (mDefaultApplicationShortcuts != null) {
+ shortcutGroups.add(mDefaultApplicationShortcuts);
}
shortcutGroups.add(getSystemShortcuts());
showKeyboardShortcutsDialog(shortcutGroups);
@@ -499,7 +501,7 @@
return systemGroup;
}
- private KeyboardShortcutGroup getDefaultApplicationShortcuts() {
+ private KeyboardShortcutGroup getDefaultApplicationShortcuts(int deviceId) {
final int userId = mContext.getUserId();
List<KeyboardShortcutInfo> keyboardShortcutInfoAppItems = new ArrayList<>();
@@ -529,65 +531,77 @@
}
}
- // Browser.
- final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
- if (browserIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
- browserIcon,
- KeyEvent.KEYCODE_B,
- KeyEvent.META_META_ON));
- }
+ CharSequence categoryTitle;
+ if (fetchBookmarksXmlKeyboardShortcuts()) {
+ KeyboardShortcutGroup apps =
+ mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ categoryTitle = apps.getLabel();
+ keyboardShortcutInfoAppItems.addAll(apps.getItems());
+ } else {
+ categoryTitle = mContext.getString(R.string.keyboard_shortcut_group_applications);
+ // Browser.
+ final Icon browserIcon = getIconForIntentCategory(Intent.CATEGORY_APP_BROWSER, userId);
+ if (browserIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ browserIcon,
+ KeyEvent.KEYCODE_B,
+ KeyEvent.META_META_ON));
+ }
- // Contacts.
- final Icon contactsIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CONTACTS, userId);
- if (contactsIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
- contactsIcon,
- KeyEvent.KEYCODE_C,
- KeyEvent.META_META_ON));
- }
+ // Contacts.
+ final Icon contactsIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CONTACTS, userId);
+ if (contactsIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ contactsIcon,
+ KeyEvent.KEYCODE_C,
+ KeyEvent.META_META_ON));
+ }
- // Email.
- final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
- if (emailIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_email),
- emailIcon,
- KeyEvent.KEYCODE_E,
- KeyEvent.META_META_ON));
- }
+ // Email.
+ final Icon emailIcon = getIconForIntentCategory(Intent.CATEGORY_APP_EMAIL, userId);
+ if (emailIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ emailIcon,
+ KeyEvent.KEYCODE_E,
+ KeyEvent.META_META_ON));
+ }
- // Messaging.
- final Icon messagingIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MESSAGING, userId);
- if (messagingIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
- messagingIcon,
- KeyEvent.KEYCODE_S,
- KeyEvent.META_META_ON));
- }
+ // Messaging.
+ final Icon messagingIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_MESSAGING, userId);
+ if (messagingIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ messagingIcon,
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON));
+ }
- // Music.
- final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
- if (musicIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_music),
- musicIcon,
- KeyEvent.KEYCODE_P,
- KeyEvent.META_META_ON));
- }
+ // Music.
+ final Icon musicIcon = getIconForIntentCategory(Intent.CATEGORY_APP_MUSIC, userId);
+ if (musicIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ musicIcon,
+ KeyEvent.KEYCODE_P,
+ KeyEvent.META_META_ON));
+ }
- // Calendar.
- final Icon calendarIcon = getIconForIntentCategory(Intent.CATEGORY_APP_CALENDAR, userId);
- if (calendarIcon != null) {
- keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
- mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
- calendarIcon,
- KeyEvent.KEYCODE_K,
- KeyEvent.META_META_ON));
+ // Calendar.
+ final Icon calendarIcon = getIconForIntentCategory(
+ Intent.CATEGORY_APP_CALENDAR, userId);
+ if (calendarIcon != null) {
+ keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ calendarIcon,
+ KeyEvent.KEYCODE_K,
+ KeyEvent.META_META_ON));
+ }
}
final int itemsSize = keyboardShortcutInfoAppItems.size();
@@ -598,7 +612,7 @@
// Sorts by label, case insensitive with nulls and/or empty labels last.
Collections.sort(keyboardShortcutInfoAppItems, mApplicationItemsComparator);
return new KeyboardShortcutGroup(
- mContext.getString(R.string.keyboard_shortcut_group_applications),
+ categoryTitle,
keyboardShortcutInfoAppItems,
true);
}
@@ -777,7 +791,8 @@
String shortcutKeyString = null;
Drawable shortcutKeyDrawable = null;
if (info.getBaseCharacter() > Character.MIN_VALUE) {
- shortcutKeyString = String.valueOf(info.getBaseCharacter());
+ shortcutKeyString = String.valueOf(info.getBaseCharacter())
+ .toUpperCase(Locale.getDefault());
} else if (mSpecialCharacterNames.get(info.getKeycode()) != null) {
shortcutKeyString = mSpecialCharacterNames.get(info.getKeycode());
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index ed1756a..11ccdff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -48,6 +51,7 @@
interactor: CallChipInteractor,
systemClock: SystemClock,
private val activityStarter: ActivityStarter,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.ongoingCallState
@@ -86,9 +90,9 @@
}
return View.OnClickListener { view ->
+ logger.log(TAG, LogLevel.INFO, {}, { "Chip clicked" })
val backgroundView =
view.requireViewById<ChipBackgroundContainer>(R.id.ongoing_activity_chip_background)
- // TODO(b/332662551): Log the click event.
// This mimics OngoingCallController#updateChipClickListener.
activityStarter.postStartActivityDismissingKeyguard(
state.intent,
@@ -108,5 +112,6 @@
R.string.ongoing_phone_call_content_description,
),
)
+ private const val TAG = "CallVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 6917f46..7c95f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -18,7 +18,10 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.policy.CastDevice
import javax.inject.Inject
@@ -38,6 +41,7 @@
constructor(
@Application private val scope: CoroutineScope,
private val mediaRouterRepository: MediaRouterRepository,
+ @StatusBarChipsLog private val logger: LogBuffer,
) {
private val activeCastDevice: StateFlow<CastDevice?> =
mediaRouterRepository.castDevices
@@ -49,8 +53,10 @@
activeCastDevice
.map {
if (it != null) {
+ logger.log(TAG, LogLevel.INFO, { str1 = it.name }, { "State: Casting($str1)" })
MediaRouterCastModel.Casting(deviceName = it.name)
} else {
+ logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
MediaRouterCastModel.DoingNothing
}
}
@@ -60,4 +66,8 @@
fun stopCasting() {
activeCastDevice.value?.let { mediaRouterRepository.stopCasting(it) }
}
+
+ companion object {
+ private const val TAG = "MediaRouter"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index f5e17df..afa9cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
import com.android.systemui.statusbar.chips.casttootherdevice.ui.view.EndCastScreenToOtherDeviceDialogDelegate
@@ -58,6 +61,7 @@
private val mediaRouterChipInteractor: MediaRouterChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
/**
* The cast chip to show, based only on MediaProjection API events.
@@ -123,6 +127,16 @@
override val chip: StateFlow<OngoingActivityChipModel> =
combine(projectionChip, routerChip) { projection, router ->
+ logger.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = projection.logName
+ str2 = router.logName
+ },
+ { "projectionChip=$str1 > routerChip=$str2" }
+ )
+
// A consequence of b/269975671 is that MediaRouter and MediaProjection APIs fire at
// different times when *screen* casting:
//
@@ -149,10 +163,13 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (projection)" })
mediaProjectionChipInteractor.stopProjecting()
}
+ /** Stops the currently active media route. */
private fun stopMediaRouterCasting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop casting requested (router)" })
mediaRouterChipInteractor.stopCasting()
}
@@ -173,6 +190,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createCastScreenToOtherDeviceDialogDelegate(state),
+ logger,
+ TAG,
),
)
}
@@ -188,6 +207,8 @@
colors = ColorsModel.Red,
createDialogLaunchOnClickListener(
createGenericCastToOtherDeviceDialogDelegate(deviceName),
+ logger,
+ TAG,
),
)
}
@@ -212,5 +233,6 @@
companion object {
@DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
+ private const val TAG = "CastToOtherVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index e201652..0c34981 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -23,8 +23,11 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
@@ -51,6 +54,7 @@
private val interactor: ScreenRecordChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
interactor.screenRecordState
@@ -76,6 +80,8 @@
startTimeMs = systemClock.elapsedRealtime(),
createDialogLaunchOnClickListener(
createDelegate(state.recordedTask),
+ logger,
+ TAG,
),
)
}
@@ -90,12 +96,18 @@
return EndScreenRecordingDialogDelegate(
endMediaProjectionDialogHelper,
context,
- stopAction = interactor::stopRecording,
+ stopAction = this::stopRecording,
recordedTask,
)
}
+ private fun stopRecording() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop recording requested" })
+ interactor.stopRecording()
+ }
+
companion object {
@DrawableRes val ICON = R.drawable.ic_screenrecord
+ private const val TAG = "ScreenRecordVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 45260e18..ddebd3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -22,7 +22,10 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
@@ -52,6 +55,7 @@
private val mediaProjectionChipInteractor: MediaProjectionChipInteractor,
private val systemClock: SystemClock,
private val endMediaProjectionDialogHelper: EndMediaProjectionDialogHelper,
+ @StatusBarChipsLog private val logger: LogBuffer,
) : OngoingActivityChipViewModel {
override val chip: StateFlow<OngoingActivityChipModel> =
mediaProjectionChipInteractor.projection
@@ -72,6 +76,7 @@
/** Stops the currently active projection. */
private fun stopProjecting() {
+ logger.log(TAG, LogLevel.INFO, {}, { "Stop sharing requested" })
mediaProjectionChipInteractor.stopProjecting()
}
@@ -87,7 +92,7 @@
colors = ColorsModel.Red,
// TODO(b/332662551): Maybe use a MediaProjection API to fetch this time.
startTimeMs = systemClock.elapsedRealtime(),
- createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state)),
+ createDialogLaunchOnClickListener(createShareToAppDialogDelegate(state), logger, TAG),
)
}
@@ -101,5 +106,6 @@
companion object {
@DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
+ private const val TAG = "ShareToAppVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
index 65f94ac..ee010f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModel.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.chips.ui.viewmodel
import android.view.View
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlinx.coroutines.flow.StateFlow
@@ -33,8 +36,11 @@
/** Creates a chip click listener that launches a dialog created by [dialogDelegate]. */
fun createDialogLaunchOnClickListener(
dialogDelegate: SystemUIDialog.Delegate,
+ @StatusBarChipsLog logger: LogBuffer,
+ tag: String,
): View.OnClickListener {
- return View.OnClickListener { view ->
+ return View.OnClickListener { _ ->
+ logger.log(tag, LogLevel.INFO, {}, { "Chip clicked" })
val dialog = dialogDelegate.createDialog()
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index b63ee4c..ca5f49d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,6 +25,7 @@
import com.android.settingslib.notification.data.repository.ZenModeRepository;
import com.android.settingslib.notification.data.repository.ZenModeRepositoryImpl;
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
@@ -288,6 +289,7 @@
@Background Handler handler
) {
return new ZenModeRepositoryImpl(context, notificationManager,
+ ZenModesBackend.getInstance(context), context.getContentResolver(),
coroutineScope, coroutineContext, handler);
}
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/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index b54f9c4..5fba615 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -275,13 +275,7 @@
if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
- combine(
- notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.isShadeFullyCollapsed
- ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
- !isKeyguardShowing && isShadeFullyCollapsed
- }
- .dumpWhileCollecting("headsUpAnimationsEnabled")
+ flowOf(true).dumpWhileCollecting("headsUpAnimationsEnabled")
}
}
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/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index 45cb52a..994a0d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -183,6 +183,7 @@
@Override
public void stopCasting(CastDevice device) {
+ // TODO(b/332662551): Convert Logcat to LogBuffer.
final boolean isProjection = device.getTag() instanceof MediaProjectionInfo;
if (DEBUG) Log.d(TAG, "stopCasting isProjection=" + isProjection);
if (isProjection) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
index 5fc160b..68edd75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastDevice.kt
@@ -38,6 +38,9 @@
) {
val isCasting = state == CastState.Connecting || state == CastState.Connected
+ val shortLogString: String =
+ "CastDevice(id=$id name=$name description=$description state=$state origin=$origin)"
+
companion object {
/** Creates a [CastDevice] based on the provided information from MediaRouter. */
fun MediaRouter.RouteInfo.toCastDevice(context: Context): CastDevice {
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/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 3f1ec85..ec9b5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,9 +20,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -67,7 +66,6 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -251,25 +249,7 @@
pip.showPictureInPictureMenu();
}
});
- pip.registerPipTransitionCallback(
- new PipTransitionController.PipTransitionCallback() {
- @Override
- public void onPipTransitionStarted(int direction, Rect pipBounds) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
- @Override
- public void onPipTransitionFinished(int direction) {
- mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
- .commitUpdate(mDisplayTracker.getDefaultDisplayId());
- }
-
- @Override
- public void onPipTransitionCanceled(int direction) {
- // No op.
- }
- }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
index b23dfdc..8595178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/TestableWindowManager.java
@@ -21,6 +21,7 @@
import android.graphics.Region;
import android.os.IBinder;
import android.view.Display;
+import android.view.KeyboardShortcutGroup;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -55,6 +56,11 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mWindowManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
+ @Override
public Region getCurrentImeTouchRegion() {
return mWindowManager.getCurrentImeTouchRegion();
}
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/data/source/TestShortcuts.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 4fba7e3..c9c39b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -189,6 +189,15 @@
listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
)
+ private val standardPackageName1 = "standard.app.group1"
+
+ private val standardAppGroup1 =
+ KeyboardShortcutGroup(
+ "Standard app group 1",
+ listOf(standardShortcutInfo1, standardShortcutInfo2, standardShortcutInfo3)
+ )
+ .apply { packageName = standardPackageName1 }
+
private val standardSubCategory1 =
ShortcutSubCategory(
standardGroup1.label!!.toString(),
@@ -230,6 +239,9 @@
)
)
+ val currentAppGroups = listOf(standardAppGroup1)
+ val currentAppPackageName = standardPackageName1
+
val systemGroups = listOf(standardGroup3, standardGroup2, standardGroup1)
val systemCategory =
ShortcutCategory(
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..69fc463 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,14 @@
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.shared.model.ShortcutCategoryType.CurrentApp
+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 +42,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 +51,19 @@
@RunWith(AndroidJUnit4::class)
class ShortcutHelperViewModelTest : SysuiTestCase() {
+ private val fakeSystemSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeMultiTaskingSource = FakeKeyboardShortcutGroupsSource()
+ private val fakeCurrentAppsSource = 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 = fakeCurrentAppsSource
}
private val testScope = kosmos.testScope
@@ -53,6 +71,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 {
@@ -194,4 +218,17 @@
assertThat(activeUiState.defaultSelectedCategory)
.isEqualTo(activeUiState.shortcutCategories.first().type)
}
+
+ @Test
+ fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ testScope.runTest {
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory)
+ .isEqualTo(CurrentApp(TestShortcuts.currentAppPackageName))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 03afcb7..e68a4a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -77,6 +77,7 @@
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
@@ -101,6 +102,7 @@
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
@@ -188,6 +190,7 @@
private @Mock ActivityTransitionAnimator mActivityTransitionAnimator;
private @Mock ScrimController mScrimController;
private @Mock IActivityTaskManager mActivityTaskManagerService;
+ private @Mock IStatusBarService mStatusBarService;
private @Mock SysuiColorExtractor mColorExtractor;
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -211,6 +214,7 @@
private @Mock SystemSettings mSystemSettings;
private @Mock SecureSettings mSecureSettings;
private @Mock AlarmManager mAlarmManager;
+ private @Mock ProcessWrapper mProcessWrapper;
private FakeSystemClock mSystemClock;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@@ -247,6 +251,7 @@
.thenReturn(mock(Flow.class));
when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(mDefaultUserId);
when(mSelectedUserInteractor.getSelectedUserId(anyBoolean())).thenReturn(mDefaultUserId);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mock(WindowRootView.class)),
@@ -1225,10 +1230,12 @@
() -> mActivityTransitionAnimator,
() -> mScrimController,
mActivityTaskManagerService,
+ mStatusBarService,
mFeatureFlags,
mSecureSettings,
mSystemSettings,
mSystemClock,
+ mProcessWrapper,
mDispatcher,
() -> mDreamViewModel,
() -> mCommunalTransitionViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index fbeb6d8..732bef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -44,6 +44,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.process.ProcessWrapper;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -68,6 +69,8 @@
private KeyguardStateController mKeyguardStateController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private ProcessWrapper mProcessWrapper;
@Captor
ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -86,13 +89,15 @@
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ when(mProcessWrapper.isSystemUser()).thenReturn(true);
mSessionTracker = new SessionTracker(
mStatusBarService,
mAuthController,
mKeyguardUpdateMonitor,
mKeyguardStateController,
- mUiEventLogger
+ mUiEventLogger,
+ mProcessWrapper
);
}
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/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 415cc7c..988769f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -471,7 +471,7 @@
}
@Test
- fun onPrepareForLaunch_paddingForLaunchAnimationIsConfigured() {
+ fun getPaddingForLaunchAnimation_onLongClickedState_paddingForLaunchAnimationIsConfigured() {
val startingWidth = 100
val startingHeight = 50
val deltaWidth = (QSTileViewImpl.LONG_PRESS_EFFECT_WIDTH_SCALE - 1f) * startingWidth
@@ -480,8 +480,8 @@
// GIVEN that long-press effect properties are initialized
tileView.initializeLongPressProperties(startingHeight, startingWidth)
- // WHEN the tile is preparing for the launch animation
- tileView.prepareForLaunch()
+ // WHEN the long-press effect has ended in the long-click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.LONG_CLICKED)
// THE animation padding corresponds to the tile's growth due to the effect
val padding = tileView.getPaddingForLaunchAnimation()
@@ -497,6 +497,22 @@
}
@Test
+ fun getPaddingForLaunchAnimation_notInLongClickState_paddingForLaunchAnimationIsEmpty() {
+ val startingWidth = 100
+ val startingHeight = 50
+
+ // GIVEN that long-press effect properties are initialized
+ tileView.initializeLongPressProperties(startingHeight, startingWidth)
+
+ // WHEN the long-press effect has ended in the click state
+ kosmos.qsLongPressEffect.setState(QSLongPressEffect.State.CLICKED)
+
+ // THE animation padding is empty
+ val padding = tileView.getPaddingForLaunchAnimation()
+ assertThat(padding.isEmpty).isTrue()
+ }
+
+ @Test
fun onActivityLaunchAnimationEnd_onFreshTile_longPressPropertiesAreReset() {
// WHEN an activity launch animation ends on a fresh tile
tileView.onActivityLaunchAnimationEnd()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 2f52248..150f53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.plugins.clocks.ClockId
import com.android.systemui.plugins.clocks.ClockMessageBuffers
import com.android.systemui.plugins.clocks.ClockMetadata
+import com.android.systemui.plugins.clocks.ClockPickerConfig
import com.android.systemui.plugins.clocks.ClockProviderPlugin
import com.android.systemui.plugins.clocks.ClockSettings
import com.android.systemui.plugins.PluginLifecycleManager
@@ -74,6 +75,7 @@
private lateinit var fakeDefaultProvider: FakeClockPlugin
private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
private lateinit var registry: ClockRegistry
+ private lateinit var pickerConfig: ClockPickerConfig
private val featureFlags = FakeFeatureFlags()
companion object {
@@ -82,9 +84,9 @@
return null!!
}
- private fun failThumbnail(clockId: ClockId): Drawable? {
- fail("Unexpected call to getThumbnail: $clockId")
- return null
+ private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
+ fail("Unexpected call to getClockPickerConfig: $clockId")
+ return null!!
}
}
@@ -123,22 +125,31 @@
private class FakeClockPlugin : ClockProviderPlugin {
private val metadata = mutableListOf<ClockMetadata>()
private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
- private val thumbnailCallbacks = mutableMapOf<ClockId, (ClockId) -> Drawable?>()
+ private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
override fun getClocks() = metadata
- override fun createClock(settings: ClockSettings): ClockController =
- createCallbacks[settings.clockId!!]!!(settings.clockId!!)
- override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+
+ override fun createClock(settings: ClockSettings): ClockController {
+ val clockId = settings.clockId ?: throw IllegalArgumentException("No clockId specified")
+ return createCallbacks[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No callback for '$clockId'")
+ }
+
+ override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
+ return pickerConfigs[clockId]?.invoke(clockId)
+ ?: throw NotImplementedError("No picker config for '$clockId'")
+ }
+
override fun initialize(buffers: ClockMessageBuffers?) { }
fun addClock(
id: ClockId,
create: (ClockId) -> ClockController = ::failFactory,
- getThumbnail: (ClockId) -> Drawable? = ::failThumbnail
+ getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
): FakeClockPlugin {
metadata.add(ClockMetadata(id))
createCallbacks[id] = create
- thumbnailCallbacks[id] = getThumbnail
+ pickerConfigs[id] = getPickerConfig
return this
}
}
@@ -148,9 +159,10 @@
scheduler = TestCoroutineScheduler()
dispatcher = StandardTestDispatcher(scheduler)
scope = TestScope(dispatcher)
+ pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
fakeDefaultProvider = FakeClockPlugin()
- .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { mockThumbnail })
+ .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
@@ -215,8 +227,8 @@
@Test
fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
val plugin1 = FakeClockPlugin()
- .addClock("clock_1", { mockClock }, { mockThumbnail })
- .addClock("clock_2", { mockClock }, { mockThumbnail })
+ .addClock("clock_1", { mockClock }, { pickerConfig })
+ .addClock("clock_2", { mockClock }, { pickerConfig })
val lifecycle1 = spy(FakeLifecycle("1", plugin1))
val plugin2 = FakeClockPlugin()
@@ -238,8 +250,8 @@
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
- assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
- assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ assertEquals(registry.getClockPickerConfig("clock_1"), pickerConfig)
+ assertEquals(registry.getClockPickerConfig("clock_2"), pickerConfig)
verify(lifecycle1, never()).unloadPlugin()
verify(lifecycle2, times(2)).unloadPlugin()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 2522ed7..bbe03f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -72,6 +72,10 @@
.thenReturn(mockSmallClockView)
whenever(layoutInflater.inflate(eq(R.layout.clock_default_large), any(), anyBoolean()))
.thenReturn(mockLargeClockView)
+ whenever(resources.getString(R.string.clock_default_name))
+ .thenReturn("DEFAULT_CLOCK_NAME")
+ whenever(resources.getString(R.string.clock_default_description))
+ .thenReturn("DEFAULT_CLOCK_DESC")
whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
.thenReturn(mockClockThumbnail)
whenever(mockSmallClockView.getLayoutParams()).thenReturn(FrameLayout.LayoutParams(10, 10))
@@ -85,7 +89,7 @@
// All providers need to provide clocks & thumbnails for exposed clocks
for (metadata in provider.getClocks()) {
assertNotNull(provider.createClock(metadata.clockId))
- assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ assertNotNull(provider.getClockPickerConfig(metadata.clockId))
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 6985a27..63e56ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -66,7 +66,9 @@
@Before
public void setUp() {
- mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager);
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
+ mKeyboardShortcutListSearch = new KeyboardShortcutListSearch(mContext, mWindowManager, -1);
mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog;
mKeyboardShortcutListSearch.mContext = mContext;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index 6ad8b8b..105cf16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -54,6 +54,7 @@
import org.mockito.stubbing.Answer;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -71,6 +72,8 @@
@Before
public void setUp() {
+ when(mWindowManager.getApplicationLaunchKeyboardShortcuts(anyInt())).thenReturn(
+ new KeyboardShortcutGroup("", Collections.emptyList()));
mKeyboardShortcuts = new KeyboardShortcuts(mContext, mWindowManager);
KeyboardShortcuts.sInstance = mKeyboardShortcuts;
mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
index 2e0c773..ca043f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipViewModelTest.kt
@@ -19,6 +19,7 @@
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel.Companion.createDialogLaunchOnClickListener
import com.android.systemui.statusbar.phone.SystemUIDialog
import kotlin.test.Test
@@ -32,7 +33,12 @@
@Test
fun createDialogLaunchOnClickListener_showsDialogOnClick() {
- val clickListener = createDialogLaunchOnClickListener(dialogDelegate)
+ val clickListener =
+ createDialogLaunchOnClickListener(
+ dialogDelegate,
+ logcatLogBuffer("OngoingActivityChipViewModelTest"),
+ "tag",
+ )
// Dialogs must be created on the main thread
context.mainExecutor.execute {
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..6ca5cd8 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
) {
@@ -39,6 +41,7 @@
}
private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList()
+ private var currentAppsShortcuts: List<KeyboardShortcutGroup> = emptyList()
init {
whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
@@ -46,6 +49,11 @@
keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts)
return@thenAnswer Unit
}
+ whenever(windowManager.requestAppKeyboardShortcuts(any(), any())).thenAnswer {
+ val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0)
+ keyboardShortcutReceiver.onKeyboardShortcutsReceived(currentAppsShortcuts)
+ return@thenAnswer Unit
+ }
repo.start()
}
@@ -57,6 +65,14 @@
this.imeShortcuts = imeShortcuts
}
+ /**
+ * Use this method to set what current app shortcuts should be returned from windowManager in
+ * tests. By default [WindowManager.requestAppKeyboardShortcuts] will return emptyList.
+ */
+ fun setCurrentAppsShortcuts(currentAppShortcuts: List<KeyboardShortcutGroup>) {
+ this.currentAppsShortcuts = currentAppShortcuts
+ }
+
fun hideThroughCloseSystemDialogs() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
@@ -79,6 +95,7 @@
}
fun toggle(deviceId: Int) {
+ fakeInputManager.addPhysicalKeyboard(deviceId)
fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
index eec9920..e1ecc51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediarouter/data/repository/MediaRouterRepositoryKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.statusbar.policy.fakeCastController
val Kosmos.realMediaRouterRepository by
@@ -25,6 +26,7 @@
MediaRouterRepositoryImpl(
scope = applicationCoroutineScope,
castController = fakeCastController,
+ logger = logcatLogBuffer("MediaRouter"),
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index ab71b5e..1e304d9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.chips.call.domain.interactor.callChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.callChipViewModel: CallChipViewModel by
@@ -29,5 +30,6 @@
interactor = callChipInteractor,
systemClock = fakeSystemClock,
activityStarter = activityStarter,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
index cb18b68..1737bc4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractorKosmos.kt
@@ -19,11 +19,13 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.mediarouter.data.repository.fakeMediaRouterRepository
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
val Kosmos.mediaRouterChipInteractor by
Kosmos.Fixture {
MediaRouterChipInteractor(
scope = applicationCoroutineScope,
mediaRouterRepository = fakeMediaRouterRepository,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
index 2335f21..3d85a4a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModelKosmos.kt
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.mediaRouterChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel by
@@ -33,5 +34,6 @@
mediaRouterChipInteractor = mediaRouterChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
index 2773f82..e4bb166 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.screenRecordChipInteractor
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.screenRecordChipViewModel: ScreenRecordChipViewModel by
@@ -31,5 +32,6 @@
interactor = screenRecordChipInteractor,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
systemClock = fakeSystemClock,
+ logger = statusBarChipsLogger,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
index 1b3108c..8ed7f96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModelKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.mediaProjectionChipInteractor
import com.android.systemui.statusbar.chips.mediaprojection.ui.view.endMediaProjectionDialogHelper
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
import com.android.systemui.util.time.fakeSystemClock
val Kosmos.shareToAppChipViewModel: ShareToAppChipViewModel by
@@ -31,5 +32,6 @@
mediaProjectionChipInteractor = mediaProjectionChipInteractor,
systemClock = fakeSystemClock,
endMediaProjectionDialogHelper = endMediaProjectionDialogHelper,
+ logger = statusBarChipsLogger,
)
}
diff --git a/services/Android.bp b/services/Android.bp
index cd974c5..dce6aa7 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -186,7 +186,15 @@
// merge all required services into one jar
// ============================================================
-java_library {
+soong_config_module_type {
+ name: "system_java_library",
+ module_type: "java_library",
+ config_namespace: "system_services",
+ bool_variables: ["without_vibrator"],
+ properties: ["vintf_fragments"],
+}
+
+system_java_library {
name: "services",
defaults: [
"services_java_defaults",
@@ -248,9 +256,19 @@
"service-sdksandbox.stubs.system_server",
],
- vintf_fragments: [
- "manifest_services.xml",
- ],
+ soong_config_variables: {
+ without_vibrator: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ ],
+ conditions_default: {
+ vintf_fragments: [
+ "manifest_services.xml",
+ "manifest_services_android.frameworks.vibrator.xml",
+ ],
+ },
+ },
+ },
required: [
"libukey2_jni_shared",
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3201223..458749d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -5052,6 +5052,9 @@
if (resolveInfo == null) {
return false;
}
+ if ("content".equals(intent.getScheme())) {
+ return false;
+ }
ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
int targetUid = targetActivityInfo.applicationInfo.uid;
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 00183ac..67985ef 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -76,6 +76,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.os.WakeLockStats;
import android.os.WorkSource;
@@ -158,6 +159,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1107,6 +1109,13 @@
FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR, pullAtomCallback);
+ if (Flags.addBatteryUsageStatsSliceAtom()) {
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ pullAtomCallback);
+ }
}
/** StatsPullAtomCallback for pulling BatteryUsageStats data. */
@@ -1115,7 +1124,7 @@
public int onPullAtom(int atomTag, List<StatsEvent> data) {
final BatteryUsageStats bus;
switch (atomTag) {
- case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET: {
@SuppressLint("MissingPermission")
final double minConsumedPowerThreshold =
DeviceConfig.getFloat(DEVICE_CONFIG_NAMESPACE,
@@ -1130,6 +1139,7 @@
.build();
bus = getBatteryUsageStats(List.of(querySinceReset)).get(0);
break;
+ }
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
final BatteryUsageStatsQuery queryPowerProfile =
new BatteryUsageStatsQuery.Builder()
@@ -1141,7 +1151,7 @@
.build();
bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
break;
- case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET:
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
final long sessionStart =
getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();
final long sessionEnd;
@@ -1158,6 +1168,31 @@
bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0);
setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);
break;
+ }
+ case FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID: {
+ if (!Flags.addBatteryUsageStatsSliceAtom()) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ @SuppressLint("MissingPermission")
+ final double minConsumedPowerThreshold =
+ DeviceConfig.getFloat(
+ DEVICE_CONFIG_NAMESPACE,
+ MIN_CONSUMED_POWER_THRESHOLD_KEY,
+ 0);
+ final long sessionStart = 0;
+ final long sessionEnd = System.currentTimeMillis();
+ final BatteryUsageStatsQuery query =
+ new BatteryUsageStatsQuery.Builder()
+ .setMaxStatsAgeMs(0)
+ .includeProcessStateData()
+ .includeVirtualUids()
+ .aggregateSnapshots(sessionStart, sessionEnd)
+ .setMinConsumedPowerThreshold(minConsumedPowerThreshold)
+ .build();
+ bus = getBatteryUsageStats(List.of(query)).get(0);
+ return StatsPerUidLogger.logStats(bus, data);
+ }
default:
throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
}
@@ -1169,6 +1204,262 @@
}
}
+ private static class StatsPerUidLogger {
+
+ private static final int STATSD_METRIC_MAX_DIMENSIONS_COUNT = 3000;
+
+ private static final int[] UID_PROCESS_STATES = {
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND,
+ BatteryConsumer.PROCESS_STATE_BACKGROUND,
+ BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE,
+ BatteryConsumer.PROCESS_STATE_CACHED
+ };
+
+ public record SessionInfo(
+ long startTs,
+ long endTs,
+ long duration,
+ int dischargePercentage,
+ long dischargeDuration) {}
+ ;
+
+ static int logStats(BatteryUsageStats bus, List<StatsEvent> data) {
+ final SessionInfo sessionInfo =
+ new SessionInfo(
+ bus.getStatsStartTimestamp(),
+ bus.getStatsEndTimestamp(),
+ bus.getStatsDuration(),
+ bus.getDischargePercentage(),
+ bus.getDischargeDurationMs());
+
+ if (DBG) {
+ Slog.d(TAG, "BatteryUsageStats dump = " + bus);
+ }
+ final BatteryConsumer deviceConsumer =
+ bus.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+
+ final float totalDeviceConsumedPowerMah = (float) deviceConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ processState,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ final int customPowerComponentCount = deviceConsumer.getCustomPowerComponentCount();
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ Process.INVALID_UID,
+ BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
+ 0,
+ totalDeviceConsumedPowerMah,
+ deviceConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+
+ final List<UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
+ uidConsumers.sort(
+ Comparator.<BatteryConsumer>comparingDouble(BatteryConsumer::getConsumedPower)
+ .reversed());
+
+ // Log single atom for BatteryUsageStats per uid/process_state/component/etc.
+ for (UidBatteryConsumer uidConsumer : uidConsumers) {
+ final int uid = uidConsumer.getUid();
+ final float totalConsumedPowerMah = (float) uidConsumer.getConsumedPower();
+
+ for (@BatteryConsumer.PowerComponent int componentId = 0;
+ componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
+ componentId++) {
+
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+
+ if (!addStatsForPredefinedComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+
+ // looping over custom components
+ for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
+ componentId
+ < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + customPowerComponentCount;
+ componentId++) {
+ for (@BatteryConsumer.ProcessState int processState : UID_PROCESS_STATES) {
+ final long timeInStateMillis =
+ uidConsumer.getTimeInProcessStateMs(processState);
+ if (timeInStateMillis <= 0) {
+ continue;
+ }
+
+ if (!addStatsForCustomComponent(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ totalConsumedPowerMah,
+ uidConsumer,
+ componentId)) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private static boolean addStatsForPredefinedComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ @BatteryConsumer.PowerComponent int componentId) {
+ final BatteryConsumer.Key key = batteryConsumer.getKey(componentId, processState);
+ if (key == null) {
+ return true;
+ }
+
+ final String powerComponentName = BatteryConsumer.powerComponentIdToString(componentId);
+ final float powerMah = (float) batteryConsumer.getConsumedPower(key);
+ final long powerComponentDurationMillis = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && powerComponentDurationMillis == 0) {
+ return true;
+ }
+
+ long timeInState = 0;
+ if (batteryConsumer instanceof UidBatteryConsumer) {
+ timeInState =
+ ((UidBatteryConsumer) batteryConsumer)
+ .getTimeInProcessStateMs(processState);
+ }
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInState,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ private static boolean addStatsForCustomComponent(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ @BatteryConsumer.ProcessState int processState,
+ long timeInStateMillis,
+ float totalConsumedPowerMah,
+ BatteryConsumer batteryConsumer,
+ int componentId) {
+
+ if (componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
+ throw new IllegalArgumentException("Invalid custom component id: " + componentId);
+ }
+
+ final float powerMah =
+ (float) batteryConsumer.getConsumedPowerForCustomComponent(componentId);
+ if (powerMah == 0) {
+ return true;
+ }
+
+ final String powerComponentName =
+ batteryConsumer.getCustomPowerComponentName(componentId);
+
+ final long powerComponentDurationMillis =
+ batteryConsumer.getUsageDurationForCustomComponentMillis(componentId);
+
+ return addStatsAtom(
+ data,
+ sessionInfo,
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerMah,
+ powerComponentDurationMillis);
+ }
+
+ /**
+ * Returns true on success and false if reached max atoms capacity and no more atoms should
+ * be added
+ */
+ private static boolean addStatsAtom(
+ List<StatsEvent> data,
+ SessionInfo sessionInfo,
+ int uid,
+ int processState,
+ long timeInStateMillis,
+ String powerComponentName,
+ float totalConsumedPowerMah,
+ float powerComponentMah,
+ long powerComponentDurationMillis) {
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.BATTERY_USAGE_STATS_PER_UID,
+ sessionInfo.startTs(),
+ sessionInfo.endTs(),
+ sessionInfo.duration(),
+ sessionInfo.dischargePercentage(),
+ sessionInfo.dischargeDuration(),
+ uid,
+ processState,
+ timeInStateMillis,
+ powerComponentName,
+ totalConsumedPowerMah,
+ powerComponentMah,
+ powerComponentDurationMillis));
+
+ // Early termination due to statsd dimensions guardrail
+ if (data.size() == STATSD_METRIC_MAX_DIMENSIONS_COUNT) {
+ Slog.w(
+ TAG,
+ "BATTERY_USAGE_STATS_PER_UID is complete reaching"
+ + " dimension guardrail");
+ return false;
+ }
+ return true;
+ }
+ }
+
@Override
@RequiresNoPermission
public boolean isCharging() {
@@ -2824,9 +3115,11 @@
pw.println(" --checkin: generate output for a checkin report; will write (and clear) the");
pw.println(" last old completed stats when they had been reset.");
pw.println(" -c: write the current stats in checkin format.");
- pw.println(" --proto: write the current aggregate stats (without history) in proto format.");
+ pw.println(
+ " --proto: write the current aggregate stats (without history) in proto format.");
pw.println(" --history: show only history data.");
- pw.println(" --history-start <num>: show only history data starting at given time offset.");
+ pw.println(
+ " --history-start <num>: show only history data starting at given time offset.");
pw.println(" --history-create-events <num>: create <num> of battery history events.");
pw.println(" --charged: only output data since last charged.");
pw.println(" --daily: only output full daily data.");
@@ -2850,12 +3143,15 @@
pw.println(" -h: print this help text.");
pw.println("Battery stats (batterystats) commands:");
pw.println(" enable|disable <option>");
- pw.println(" Enable or disable a running option. Option state is not saved across boots.");
+ pw.println(
+ " Enable or disable a running option. Option state is not saved across boots.");
pw.println(" Options are:");
pw.println(" full-history: include additional detailed events in battery history:");
pw.println(" wake_lock_in, alarms and proc events");
pw.println(" no-auto-reset: don't automatically reset stats when unplugged");
- pw.println(" pretend-screen-off: pretend the screen is off, even if screen state changes");
+ pw.println(
+ " pretend-screen-off: pretend the screen is off, even if screen state"
+ + " changes");
}
private void dumpSettings(PrintWriter pw) {
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/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 222c5a8..12ec248 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.hardware.display.BrightnessInfo;
import android.text.TextUtils;
import com.android.server.display.brightness.BrightnessEvent;
@@ -50,6 +51,8 @@
private final boolean mIsUserInitiatedChange;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mHdrBrightness = builder.getHdrBrightness();
@@ -64,6 +67,7 @@
mBrightnessEvent = builder.getBrightnessEvent();
mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
mIsUserInitiatedChange = builder.isUserInitiatedChange();
+ mBrightnessMaxReason = builder.getBrightnessMaxReason();
}
/**
@@ -159,6 +163,13 @@
return mIsUserInitiatedChange;
}
+ /**
+ * Gets reason for max brightness restriction
+ */
+ public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -180,6 +191,8 @@
.append(Objects.toString(mBrightnessEvent, "null"));
stringBuilder.append("\n mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
stringBuilder.append("\n mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
+ stringBuilder.append("\n mBrightnessMaxReason:")
+ .append(BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
return stringBuilder.toString();
}
@@ -212,7 +225,8 @@
== otherState.shouldUpdateScreenBrightnessSetting()
&& Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
&& mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
- && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
+ && mIsUserInitiatedChange == otherState.isUserInitiatedChange()
+ && mBrightnessMaxReason == otherState.getBrightnessMaxReason();
}
@Override
@@ -221,7 +235,7 @@
mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
mCustomAnimationRate,
mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
- mIsUserInitiatedChange);
+ mIsUserInitiatedChange, mBrightnessMaxReason);
}
/**
@@ -245,12 +259,11 @@
private float mMinBrightness;
private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
private boolean mShouldUpdateScreenBrightnessSetting;
-
private BrightnessEvent mBrightnessEvent;
-
- public int mBrightnessAdjustmentFlag = 0;
-
+ private int mBrightnessAdjustmentFlag = 0;
private boolean mIsUserInitiatedChange;
+ private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
+ BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
/**
* Create a builder starting with the values from the specified {@link
@@ -274,6 +287,7 @@
builder.setBrightnessEvent(state.getBrightnessEvent());
builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
+ builder.setBrightnessMaxReason(state.getBrightnessMaxReason());
return builder;
}
@@ -496,5 +510,21 @@
mIsUserInitiatedChange = isUserInitiatedChange;
return this;
}
+
+ /**
+ * Gets reason for max brightness restriction
+ */
+ public @BrightnessInfo.BrightnessMaxReason int getBrightnessMaxReason() {
+ return mBrightnessMaxReason;
+ }
+
+ /**
+ * Sets reason for max brightness restriction
+ */
+ public Builder setBrightnessMaxReason(
+ @BrightnessInfo.BrightnessMaxReason int brightnessMaxReason) {
+ mBrightnessMaxReason = brightnessMaxReason;
+ return this;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58309c2..01604b8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1580,7 +1580,7 @@
// brightness sources (such as an app override) are not saved to the setting, but should be
// reflected in HBM calculations.
mBrightnessRangeController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
- mBrightnessClamperController.getBrightnessMaxReason());
+ clampedState.getBrightnessMaxReason());
// Animate the screen brightness when the screen is on or dozing.
// Skip the animation when the screen is off or suspended.
@@ -1783,7 +1783,7 @@
if (userSetBrightnessChanged
|| newEvent.getReason().getReason() != BrightnessReason.REASON_TEMPORARY) {
- logBrightnessEvent(newEvent, unthrottledBrightnessState);
+ logBrightnessEvent(newEvent, unthrottledBrightnessState, clampedState);
}
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
@@ -1976,6 +1976,9 @@
synchronized (mCachedBrightnessInfo) {
float stateMax = state != null ? state.getMaxBrightness() : PowerManager.BRIGHTNESS_MAX;
float stateMin = state != null ? state.getMinBrightness() : PowerManager.BRIGHTNESS_MAX;
+ @BrightnessInfo.BrightnessMaxReason int maxReason =
+ state != null ? state.getBrightnessMaxReason()
+ : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
final float minBrightness = Math.max(stateMin, Math.min(
mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
final float maxBrightness = Math.min(
@@ -2002,7 +2005,7 @@
mBrightnessRangeController.getTransitionPoint());
changed |=
mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
- mBrightnessClamperController.getBrightnessMaxReason());
+ maxReason);
return changed;
}
}
@@ -2902,7 +2905,8 @@
return FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__ENTIRE_REASON__REASON_UNKNOWN;
}
- private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness) {
+ private void logBrightnessEvent(BrightnessEvent event, float unmodifiedBrightness,
+ DisplayBrightnessState brightnessState) {
int modifier = event.getReason().getModifier();
int flags = event.getFlags();
// It's easier to check if the brightness is at maximum level using the brightness
@@ -2939,7 +2943,7 @@
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
(modifier & BrightnessReason.MODIFIER_LOW_POWER) > 0,
- mBrightnessClamperController.getBrightnessMaxReason(),
+ brightnessState.getBrightnessMaxReason(),
// TODO: (flc) add brightnessMinReason here too.
(modifier & BrightnessReason.MODIFIER_DIMMED) > 0,
event.isRbcEnabled(),
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 88d2c00..d1fb009 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -139,6 +139,7 @@
builder.setBrightness(cappedBrightness);
builder.setMaxBrightness(mBrightnessCap);
builder.setCustomAnimationRate(mCustomAnimationRate);
+ builder.setBrightnessMaxReason(getBrightnessMaxReason());
if (mClamperType != null) {
builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -163,19 +164,8 @@
return builder.build();
}
- /**
- * See BrightnessThrottler.getBrightnessMaxReason:
- * used in:
- * 1) DPC2.CachedBrightnessInfo to determine changes
- * 2) DPC2.logBrightnessEvent
- * 3) HBMController - for logging
- * Method is called in mHandler thread (DisplayControllerHandler), in the same thread
- * recalculateBrightnessCap and DPC2.updatePowerStateInternal are called.
- * Should be moved to DisplayBrightnessState OR derived from DisplayBrightnessState
- * TODO: b/263362199
- */
@BrightnessInfo.BrightnessMaxReason
- public int getBrightnessMaxReason() {
+ private int getBrightnessMaxReason() {
if (mClamperType == null) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
} else if (mClamperType == Type.THERMAL) {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 91ab872..9968590 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -18,20 +18,14 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.UserInfo;
import android.os.Handler;
import android.os.Process;
import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
@@ -225,49 +219,12 @@
sWriter.startThread();
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
+ @AnyThread
+ static void remove(@UserIdInt int userId, @NonNull Handler ioHandler) {
+ sWriter.onUserRemoved(userId);
ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserCreated(UserInfo user, @Nullable Object token) {
- final int userId = user.id;
- sWriter.onUserCreated(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- if (!sPerUserMap.contains(userId)) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeUtils.load(userId);
- sPerUserMap.put(userId, additionalSubtypeMap);
- final InputMethodSettings settings =
- InputMethodManagerService
- .queryInputMethodServicesInternal(context,
- userId,
- additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, settings);
- }
- }
- });
- }
-
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- sWriter.onUserRemoved(userId);
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- sPerUserMap.put(userId, AdditionalSubtypeUtils.load(userId));
- }
+ sPerUserMap.remove(userId);
}
});
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 5ab493b..9837ab1 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -408,7 +408,8 @@
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
}
- mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
+ mService.initializeImeLocked(mCurMethod, mCurToken,
+ InputMethodBindingController.this);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked(mUserId);
mAutofillController.performOnCreateInlineSuggestionsRequest();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
new file mode 100644
index 0000000..b835d05
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDrawsNavBarResourceMonitor.java
@@ -0,0 +1,89 @@
+/*
+ * 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.inputmethod;
+
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.PatternMatcher;
+import android.os.UserHandle;
+import android.util.Slog;
+
+final class InputMethodDrawsNavBarResourceMonitor {
+ private static final String TAG = "InputMethodDrawsNavBarResourceMonitor";
+
+ private static final String SYSTEM_PACKAGE_NAME = "android";
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodDrawsNavBarResourceMonitor() {
+ }
+
+ @WorkerThread
+ static boolean evaluate(@NonNull Context context, @UserIdInt int userId) {
+ final Context userAwareContext;
+ if (context.getUserId() == userId) {
+ userAwareContext = context;
+ } else {
+ userAwareContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+ }
+ try {
+ return userAwareContext.getPackageManager()
+ .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
+ .getBoolean(com.android.internal.R.bool.config_imeDrawsImeNavBar);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed",
+ e);
+ return false;
+ }
+ }
+
+ @FunctionalInterface
+ interface OnUpdateCallback {
+ void onUpdate(@UserIdInt int userId);
+ }
+
+ @SuppressLint("MissingPermission")
+ @AnyThread
+ static void registerCallback(@NonNull Context context, @NonNull Handler ioHandler,
+ @NonNull OnUpdateCallback callback) {
+ final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+ intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
+
+ final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int userId = getSendingUserId();
+ callback.onUpdate(userId);
+ }
+ };
+ context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, intentFilter,
+ null /* broadcastPermission */, ioHandler, Context.RECEIVER_NOT_EXPORTED);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7daf958..e342c78 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -67,6 +67,7 @@
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -169,7 +170,6 @@
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.TransferPipe;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.AccessibilityManagerInternal;
@@ -202,7 +202,6 @@
import java.util.OptionalInt;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -399,15 +398,6 @@
@SharedByAllUsersField
private IntArray mStylusIds;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- private OverlayableSystemBooleanResourceWrapper mImeDrawsImeNavBarRes;
- @GuardedBy("ImfLock.class")
- @Nullable
- @MultiUserUnawareField
- Future<?> mImeDrawsImeNavBarResLazyInitFuture;
-
private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
/**
* {@inheritDoc}
@@ -484,13 +474,13 @@
@SharedByAllUsersField
boolean mSystemReady;
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
UserDataRepository.UserData getUserData(@UserIdInt int userId) {
return mUserDataRepository.getOrCreate(userId);
}
- @GuardedBy("ImfLock.class")
+ @AnyThread
@NonNull
InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
return getUserData(userId).mBindingController;
@@ -933,6 +923,14 @@
// For production code, hook up user lifecycle
mService.mUserManagerInternal.addUserLifecycleListener(this);
+
+ // Hook up resource change first before initializeUsersAsync() starts reading the
+ // seemingly initial data so that we can eliminate the race condition.
+ InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler,
+ mService::onUpdateResourceOverlay);
+
+ // Also schedule user init tasks onto an I/O thread.
+ initializeUsersAsync(mService.mUserManagerInternal.getUserIds());
}
@VisibleForTesting
@@ -1015,6 +1013,7 @@
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
// Called directly from UserManagerService. Do not block the calling thread.
+ initializeUsersAsync(new int[user.id]);
}
@Override
@@ -1022,6 +1021,8 @@
// Called directly from UserManagerService. Do not block the calling thread.
final int userId = user.id;
SecureSettingsWrapper.onUserRemoved(userId);
+ AdditionalSubtypeMapRepository.remove(userId, mService.mIoHandler);
+ InputMethodSettingsRepository.remove(userId);
mService.mUserDataRepository.remove(userId);
}
@@ -1049,6 +1050,44 @@
});
}
+ @AnyThread
+ private void initializeUsersAsync(@UserIdInt int[] userIds) {
+ mService.mIoHandler.post(() -> {
+ final var service = mService;
+ final var context = service.mContext;
+ final var userManagerInternal = service.mUserManagerInternal;
+
+ // We first create InputMethodMap for each user without loading AdditionalSubtypes.
+ final int numUsers = userIds.length;
+ final InputMethodMap[] rawMethodMaps = new InputMethodMap[numUsers];
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ rawMethodMaps[i] = InputMethodManagerService.queryInputMethodServicesInternal(
+ context, userId, AdditionalSubtypeMap.EMPTY_MAP,
+ DirectBootAwareness.AUTO).getMethodMap();
+ final int profileParentId = userManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(context,
+ profileParentId);
+ final var userData = mService.getUserData(userId);
+ userData.mImeDrawsNavBar.set(value);
+ }
+
+ // Then create full InputMethodMap for each user. Note that
+ // AdditionalSubtypeMapRepository#get() and InputMethodSettingsRepository#put()
+ // need to be called with ImfLock held (b/352387655).
+ // TODO(b/343601565): Avoid ImfLock after fixing b/352387655.
+ synchronized (ImfLock.class) {
+ for (int i = 0; i < numUsers; ++i) {
+ final int userId = userIds[i];
+ final var map = AdditionalSubtypeMapRepository.get(userId);
+ final var methodMap = rawMethodMaps[i].applyAdditionalSubtypes(map);
+ final var settings = InputMethodSettings.create(methodMap, userId);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
+ }
+ });
+ }
}
void onUnlockUser(@UserIdInt int userId) {
@@ -1121,16 +1160,6 @@
mShowOngoingImeSwitcherForPhones = false;
- // Executing InputMethodSettingsRepository.initialize() does not mean that it
- // immediately becomes ready to return the up-to-date InputMethodSettings for each
- // running user, because we want to return from the constructor as early as possible so
- // as not to delay the system boot process.
- // Search for InputMethodSettingsRepository.put() to find where and when it's actually
- // being updated. In general IMMS should refrain from exposing the existence of IMEs
- // until systemReady().
- InputMethodSettingsRepository.initialize(mIoHandler, mContext);
- AdditionalSubtypeMapRepository.initialize(mIoHandler, mContext);
-
mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
@SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
bindingControllerFactory = userId -> new InputMethodBindingController(userId,
@@ -1216,36 +1245,6 @@
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
}
- @GuardedBy("ImfLock.class")
- private void maybeInitImeNavbarConfigLocked(@UserIdInt int targetUserId) {
- // Currently, com.android.internal.R.bool.config_imeDrawsImeNavBar is overlaid only for the
- // profile parent user.
- // TODO(b/221443458): See if we can make OverlayManager be aware of profile groups.
- final int profileParentUserId = mUserManagerInternal.getProfileParentId(targetUserId);
- if (mImeDrawsImeNavBarRes != null
- && mImeDrawsImeNavBarRes.getUserId() != profileParentUserId) {
- mImeDrawsImeNavBarRes.close();
- mImeDrawsImeNavBarRes = null;
- }
- if (mImeDrawsImeNavBarRes == null) {
- final Context userContext;
- if (mContext.getUserId() == profileParentUserId) {
- userContext = mContext;
- } else {
- userContext = mContext.createContextAsUser(UserHandle.of(profileParentUserId),
- 0 /* flags */);
- }
- mImeDrawsImeNavBarRes = OverlayableSystemBooleanResourceWrapper.create(userContext,
- com.android.internal.R.bool.config_imeDrawsImeNavBar, mHandler, resource -> {
- synchronized (ImfLock.class) {
- if (resource == mImeDrawsImeNavBarRes) {
- sendOnNavButtonFlagsChangedLocked();
- }
- }
- });
- }
- }
-
@NonNull
private static PackageManager getPackageManagerForUser(@NonNull Context context,
@UserIdInt int userId) {
@@ -1278,8 +1277,6 @@
// Hereafter we start initializing things for "newUserId".
- maybeInitImeNavbarConfigLocked(newUserId);
-
final var newUserData = getUserData(newUserId);
// TODO(b/342027196): Double check if we need to always reset upon user switching.
@@ -1358,23 +1355,6 @@
});
}
- // TODO(b/32343335): The entire systemRunning() method needs to be revisited.
- mImeDrawsImeNavBarResLazyInitFuture = SystemServerInitThreadPool.submit(() -> {
- // Note that the synchronization block below guarantees that the task
- // can never be completed before the returned Future<?> object is assigned to
- // the "mImeDrawsImeNavBarResLazyInitFuture" field.
- synchronized (ImfLock.class) {
- mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mCurrentUserId) {
- // This means that the current user is already switched to other user
- // before the background task is executed. In this scenario the relevant
- // field should already be initialized.
- return;
- }
- maybeInitImeNavbarConfigLocked(currentUserId);
- }
- }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes");
-
mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler);
SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(),
new String[] {
@@ -1896,7 +1876,8 @@
userData.mCurClient.mUid, true /* direct */);
}
- @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
+ @InputMethodNavButtonFlags final int navButtonFlags =
+ getInputMethodNavButtonFlagsLocked(userData);
final SessionState session = userData.mCurClient.mCurSession;
setEnabledSessionLocked(session, userData);
session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
@@ -2288,15 +2269,15 @@
@GuardedBy("ImfLock.class")
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
- @UserIdInt int userId) {
+ @NonNull InputMethodBindingController bindingController) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + getInputMethodBindingController(userId).getCurTokenDisplayId());
+ + bindingController.getCurTokenDisplayId());
}
+ final int userId = bindingController.getUserId();
inputMethod.initializeInternal(token,
new InputMethodPrivilegedOperationsImpl(this, token, userId),
- // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
- getInputMethodNavButtonFlagsLocked());
+ getInputMethodNavButtonFlagsLocked(getUserData(userId)));
}
@AnyThread
@@ -2595,23 +2576,17 @@
@GuardedBy("ImfLock.class")
@InputMethodNavButtonFlags
- private int getInputMethodNavButtonFlagsLocked() {
- // TODO(b/345519864): Make mImeDrawsImeNavBarRes multi-user aware.
- final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
- if (mImeDrawsImeNavBarResLazyInitFuture != null) {
- // TODO(b/225366708): Avoid Future.get(), which is internally used here.
- ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
- "Waiting for the lazy init of mImeDrawsImeNavBarRes");
- }
+ private int getInputMethodNavButtonFlagsLocked(
+ @NonNull UserDataRepository.UserData userData) {
+ final int userId = userData.mUserId;
+ final var bindingController = userData.mBindingController;
// Whether the current display has a navigation bar. When this is false (e.g. emulator),
// the IME should not draw the IME navigation bar.
final int tokenDisplayId = bindingController.getCurTokenDisplayId();
final boolean hasNavigationBar = mWindowManagerInternal
.hasNavigationBar(tokenDisplayId != INVALID_DISPLAY
? tokenDisplayId : DEFAULT_DISPLAY);
- final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
+ final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId);
return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2955,7 +2930,7 @@
final var userData = getUserData(userId);
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
}
@GuardedBy("ImfLock.class")
@@ -4979,7 +4954,7 @@
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
synchronized (ImfLock.class) {
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
case MSG_SYSTEM_UNLOCK_USER: {
@@ -5313,7 +5288,7 @@
userData.mSwitchingController.resetCircularListLocked(mContext, settings);
userData.mHardwareKeyboardShortcutController.update(settings);
- sendOnNavButtonFlagsChangedLocked();
+ sendOnNavButtonFlagsChangedLocked(userData);
// Notify InputMethodListListeners of the new installed InputMethods.
final List<InputMethodInfo> inputMethodList = settings.getMethodList();
@@ -5322,14 +5297,38 @@
}
@GuardedBy("ImfLock.class")
- void sendOnNavButtonFlagsChangedLocked() {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ void sendOnNavButtonFlagsChangedToAllImesLocked() {
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ sendOnNavButtonFlagsChangedLocked(getUserData(userId));
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ void sendOnNavButtonFlagsChangedLocked(@NonNull UserDataRepository.UserData userData) {
+ final var bindingController = userData.mBindingController;
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
// No need to send the data if the IME is not yet bound.
return;
}
- curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked());
+ curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData));
+ }
+
+ @WorkerThread
+ private void onUpdateResourceOverlay(@UserIdInt int userId) {
+ final int profileParentId = mUserManagerInternal.getProfileParentId(userId);
+ final boolean value =
+ InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId);
+ final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false);
+ final ArrayList<UserDataRepository.UserData> updatedUsers = new ArrayList<>();
+ for (int profileUserId : profileUserIds) {
+ final var userData = getUserData(profileUserId);
+ userData.mImeDrawsNavBar.set(value);
+ updatedUsers.add(userData);
+ }
+ synchronized (ImfLock.class) {
+ updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked);
+ }
}
@GuardedBy("ImfLock.class")
@@ -6097,6 +6096,7 @@
u.mImeBindingState.dump(" ", p);
p.println(" enabledSession=" + u.mEnabledSession);
p.println(" inFullscreenMode=" + u.mInFullscreenMode);
+ p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get());
p.println(" switchingController:");
u.mSwitchingController.dump(p, " ");
p.println(" mLastEnabledInputMethodsStr="
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 656c87d..06f73f3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -202,7 +202,7 @@
attrs.setTitle("Select input method");
w.setAttributes(attrs);
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedLocked(mService.getUserData(userId));
mSwitchingDialog.show();
}
@@ -242,7 +242,7 @@
// TODO(b/305849394): Make InputMethodMenuController multi-user aware
final int userId = mService.getCurrentImeUserIdLocked();
mService.updateSystemUiLocked(userId);
- mService.sendOnNavButtonFlagsChangedLocked();
+ mService.sendOnNavButtonFlagsChangedToAllImesLocked();
mDialogBuilder = null;
mIms = null;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index a4d8ee5..50ba364 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -16,17 +16,12 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
final class InputMethodSettingsRepository {
@GuardedBy("ImfLock.class")
@@ -54,33 +49,10 @@
sPerUserMap.put(userId, obj);
}
- static void initialize(@NonNull Handler ioHandler, @NonNull Context context) {
- final UserManagerInternal userManagerInternal =
- LocalServices.getService(UserManagerInternal.class);
- ioHandler.post(() -> {
- userManagerInternal.addUserLifecycleListener(
- new UserManagerInternal.UserLifecycleListener() {
- @Override
- public void onUserRemoved(UserInfo user) {
- final int userId = user.id;
- ioHandler.post(() -> {
- synchronized (ImfLock.class) {
- sPerUserMap.remove(userId);
- }
- });
- }
- });
- synchronized (ImfLock.class) {
- for (int userId : userManagerInternal.getUserIds()) {
- final InputMethodSettings settings =
- InputMethodManagerService.queryInputMethodServicesInternal(
- context,
- userId,
- AdditionalSubtypeMapRepository.get(userId),
- DirectBootAwareness.AUTO);
- put(userId, settings);
- }
- }
- });
+ @AnyThread
+ static void remove(@UserIdInt int userId) {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java b/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
deleted file mode 100644
index 33e7a76..0000000
--- a/services/core/java/com/android/server/inputmethod/OverlayableSystemBooleanResourceWrapper.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.inputmethod;
-
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-
-import android.annotation.AnyThread;
-import android.annotation.BoolRes;
-import android.annotation.NonNull;
-import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Handler;
-import android.os.PatternMatcher;
-import android.util.Slog;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-
-/**
- * A wrapper object for any boolean resource defined in {@code "android"} package, in a way that is
- * aware of per-user Runtime Resource Overlay (RRO).
- */
-final class OverlayableSystemBooleanResourceWrapper implements AutoCloseable {
- private static final String TAG = "OverlayableSystemBooleanResourceWrapper";
-
- private static final String SYSTEM_PACKAGE_NAME = "android";
-
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final AtomicBoolean mValueRef;
- @NonNull
- private final AtomicReference<Runnable> mCleanerRef;
-
- /**
- * Creates {@link OverlayableSystemBooleanResourceWrapper} for the given boolean resource ID
- * with a value change callback for the user associated with the {@link Context}.
- *
- * @param userContext The {@link Context} to be used to access the resource. This needs to be
- * associated with the right user because the Runtime Resource Overlay (RRO)
- * is per-user configuration.
- * @param boolResId The resource ID to be queried.
- * @param handler {@link Handler} to be used to dispatch {@code callback}.
- * @param callback The callback to be notified when the specified value might be updated.
- * The callback needs to take care of spurious wakeup. The value returned from
- * {@link #get()} may look to be exactly the same as the previously read value
- * e.g. when the value is changed from {@code false} to {@code true} to
- * {@code false} in a very short period of time, because {@link #get()} always
- * does volatile-read.
- * @return New {@link OverlayableSystemBooleanResourceWrapper}.
- */
- @NonNull
- @UserHandleAware
- static OverlayableSystemBooleanResourceWrapper create(@NonNull Context userContext,
- @BoolRes int boolResId, @NonNull Handler handler,
- @NonNull Consumer<OverlayableSystemBooleanResourceWrapper> callback) {
-
- // Note that we cannot fully trust this initial value due to the dead time between obtaining
- // the value here and setting up a broadcast receiver for change callback below.
- // We will refresh the value again later after setting up the change callback anyway.
- final AtomicBoolean valueRef = new AtomicBoolean(evaluate(userContext, boolResId));
-
- final AtomicReference<Runnable> cleanerRef = new AtomicReference<>();
-
- final OverlayableSystemBooleanResourceWrapper object =
- new OverlayableSystemBooleanResourceWrapper(userContext.getUserId(), valueRef,
- cleanerRef);
-
- final IntentFilter intentFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- intentFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
- intentFilter.addDataSchemeSpecificPart(SYSTEM_PACKAGE_NAME, PatternMatcher.PATTERN_LITERAL);
-
- final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final boolean newValue = evaluate(userContext, boolResId);
- if (newValue != valueRef.getAndSet(newValue)) {
- callback.accept(object);
- }
- }
- };
- userContext.registerReceiver(broadcastReceiver, intentFilter,
- null /* broadcastPermission */, handler,
- Context.RECEIVER_NOT_EXPORTED);
- cleanerRef.set(() -> userContext.unregisterReceiver(broadcastReceiver));
-
- // Make sure that the initial observable value is obtained after the change callback is set.
- valueRef.set(evaluate(userContext, boolResId));
- return object;
- }
-
- private OverlayableSystemBooleanResourceWrapper(@UserIdInt int userId,
- @NonNull AtomicBoolean valueRef, @NonNull AtomicReference<Runnable> cleanerRef) {
- mUserId = userId;
- mValueRef = valueRef;
- mCleanerRef = cleanerRef;
- }
-
- /**
- * @return The boolean resource value.
- */
- @AnyThread
- boolean get() {
- return mValueRef.get();
- }
-
- /**
- * @return The user ID associated with this resource reader.
- */
- @AnyThread
- @UserIdInt
- int getUserId() {
- return mUserId;
- }
-
- @AnyThread
- private static boolean evaluate(@NonNull Context context, @BoolRes int boolResId) {
- try {
- return context.getPackageManager()
- .getResourcesForApplication(SYSTEM_PACKAGE_NAME)
- .getBoolean(boolResId);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "getResourcesForApplication(\"" + SYSTEM_PACKAGE_NAME + "\") failed", e);
- return false;
- }
- }
-
- /**
- * Cleans up the callback.
- */
- @AnyThread
- @Override
- public void close() {
- final Runnable cleaner = mCleanerRef.getAndSet(null);
- if (cleaner != null) {
- cleaner.run();
- }
- }
-}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 98d7548..7c68d54 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -29,6 +29,7 @@
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
@@ -181,6 +182,12 @@
String mLastEnabledInputMethodsStr = "";
/**
+ * {@code true} when the IME is responsible for drawing the navigation bar and its buttons.
+ */
+ @NonNull
+ final AtomicBoolean mImeDrawsNavBar = new AtomicBoolean();
+
+ /**
* Intended to be instantiated only from this file.
*/
private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
index 058c1c8..e1b1416 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEventLogger.java
@@ -16,10 +16,12 @@
package com.android.server.location.contexthub;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
import java.util.Collection;
+import java.util.Optional;
/**
* A class to log events and useful metrics within the Context Hub service.
@@ -149,10 +151,20 @@
*/
public final NanoAppMessage message;
+ /**
+ * the error code for the message
+ */
+ public Optional<Byte> errorCode;
+
public NanoappMessageEvent(long mTimeStampInMs, int mContextHubId,
NanoAppMessage mMessage, boolean mSuccess) {
super(mTimeStampInMs, mContextHubId, 0, mSuccess);
message = mMessage;
+ errorCode = Optional.empty();
+ }
+
+ public void setErrorCode(byte errorCode) {
+ this.errorCode = Optional.of(errorCode);
}
@Override
@@ -165,6 +177,8 @@
sb.append(message.toString());
sb.append(", success = ");
sb.append(success ? "true" : "false");
+ sb.append(", errorCode = ");
+ sb.append(errorCode.isPresent() ? errorCode.get() : "null");
sb.append(']');
return sb.toString();
}
@@ -312,6 +326,28 @@
}
/**
+ * Logs the status of a reliable message
+ *
+ * @param messageSequenceNumber the message sequence number
+ * @param errorCode the error code
+ */
+ public synchronized void logReliableMessageToNanoappStatus(
+ int messageSequenceNumber, byte errorCode) {
+ if (!Flags.reliableMessage()) {
+ return;
+ }
+
+ for (NanoappMessageEvent event : mMessageToNanoappQueue) {
+ if (event.message.isReliable()
+ && event.message.getMessageSequenceNumber()
+ == messageSequenceNumber) {
+ event.setErrorCode(errorCode);
+ break;
+ }
+ }
+ }
+
+ /**
* Logs a context hub restart event
*
* @param contextHubId the ID of the context hub
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index a0aad52..ed451ff 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1087,6 +1087,8 @@
* @param messageDeliveryStatus The message delivery status to deliver.
*/
private void handleMessageDeliveryStatusCallback(MessageDeliveryStatus messageDeliveryStatus) {
+ ContextHubEventLogger.getInstance().logReliableMessageToNanoappStatus(
+ messageDeliveryStatus.messageSequenceNumber, messageDeliveryStatus.errorCode);
mTransactionManager.onMessageDeliveryResponse(messageDeliveryStatus.messageSequenceNumber,
messageDeliveryStatus.errorCode == ErrorCode.OK);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 56e4590..46585a5 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -80,6 +80,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.content.PackageMonitor;
import com.android.internal.content.om.OverlayConfig;
import com.android.internal.util.ArrayUtils;
@@ -261,6 +262,7 @@
private final OverlayActorEnforcer mActorEnforcer;
+ @KeepForWeakReference
private final PackageMonitor mPackageMonitor = new OverlayManagerPackageMonitor();
private int mPrevStartedUserId = -1;
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index c5e2bb8..8410cff 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -34,6 +34,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -370,8 +371,13 @@
}
private void enforceUid(int callingUid) {
- if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT) {
- throw new SecurityException("uid " + callingUid + " not allowed to access PDB");
+ enforceUid(callingUid, /* allowShell= */ false);
+ }
+
+ private void enforceUid(int callingUid, boolean allowShell) {
+ if (callingUid != mAllowedUid && callingUid != UserHandle.AID_ROOT
+ && (callingUid != Process.SHELL_UID || !allowShell)) {
+ throw new SecurityException("Uid " + callingUid + " not allowed to access PDB");
}
}
@@ -864,7 +870,8 @@
private final IBinder mService = new IPersistentDataBlockService.Stub() {
private int printFrpStatus(PrintWriter pw, boolean printSecrets) {
- enforceUid(Binder.getCallingUid());
+ // Only allow SHELL_UID to print the status if printing the secrets is disabled
+ enforceUid(Binder.getCallingUid(), /* allowShell= */ !printSecrets);
pw.println("FRP state");
pw.println("=========");
@@ -872,8 +879,14 @@
pw.println("FRP state: " + mFrpActive);
printFrpDataFilesContents(pw, printSecrets);
printFrpSecret(pw, printSecrets);
- pw.println("OEM unlock state: " + getOemUnlockEnabled());
- pw.println("Bootloader lock state: " + getFlashLockState());
+
+ // Do not print OEM unlock state and flash lock state if the caller is a non-root
+ // shell - it likely won't have permissions anyways.
+ if (Binder.getCallingUid() != Process.SHELL_UID) {
+ pw.println("OEM unlock state: " + getOemUnlockEnabled());
+ pw.println("Bootloader lock state: " + getFlashLockState());
+ }
+
pw.println("Verified boot state: " + getVerifiedBootState());
pw.println("Has FRP credential handle: " + hasFrpCredentialHandle());
pw.println("FRP challenge block size: " + getDataBlockSize());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b5c33cd..07c8ee7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -186,7 +186,6 @@
import com.android.internal.pm.pkg.component.ParsedMainComponent;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
import com.android.internal.telephony.CarrierAppUtils;
-import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -4493,7 +4492,7 @@
void setSystemAppHiddenUntilInstalled(@NonNull Computer snapshot, String packageName,
boolean hidden) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = TelephonyPermissions.isSystemOrPhone(callingUid);
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -4518,8 +4517,7 @@
boolean setSystemAppInstallState(@NonNull Computer snapshot, String packageName,
boolean installed, int userId) {
final int callingUid = Binder.getCallingUid();
- final boolean calledFromSystemOrPhone = callingUid == Process.PHONE_UID
- || callingUid == Process.SYSTEM_UID;
+ final boolean calledFromSystemOrPhone = isSystemOrPhone(callingUid);
if (!calledFromSystemOrPhone) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
"setSystemAppHiddenUntilInstalled");
@@ -8123,4 +8121,9 @@
PackageManager.VERSION_CODE_HIGHEST, UserHandle.USER_SYSTEM,
PackageManager.DELETE_ALL_USERS, true /*removedBySystem*/);
}
+
+ private static boolean isSystemOrPhone(int uid) {
+ return UserHandle.isSameApp(uid, Process.SYSTEM_UID)
+ || UserHandle.isSameApp(uid, Process.PHONE_UID);
+ }
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 3a79d0d..fde23b7 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -22,8 +22,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.RemoteException;
@@ -36,7 +38,11 @@
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IShortcutService;
import com.android.internal.util.XmlUtils;
import com.android.server.input.KeyboardMetricsCollector;
@@ -46,7 +52,9 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
/**
@@ -183,8 +191,12 @@
String rolePackage = mRoleManager.getDefaultApplication(role);
if (rolePackage != null) {
intent = mPackageManager.getLaunchIntentForPackage(rolePackage);
- intent.putExtra(EXTRA_ROLE, role);
- mRoleIntents.put(role, intent);
+ if (intent != null) {
+ intent.putExtra(EXTRA_ROLE, role);
+ mRoleIntents.put(role, intent);
+ } else {
+ Log.w(TAG, "No launch intent for role " + role);
+ }
} else {
Log.w(TAG, "No default application for role " + role);
}
@@ -198,8 +210,7 @@
private void loadShortcuts() {
try {
- XmlResourceParser parser = mContext.getResources().getXml(
- com.android.internal.R.xml.bookmarks);
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
while (true) {
@@ -270,6 +281,9 @@
continue;
}
intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
+ if (intent == null) {
+ Log.w(TAG, "Null selector intent for " + categoryName);
+ }
} else if (roleName != null) {
// We can't resolve the role at the time of this file being parsed as the
// device hasn't finished booting, so we will look it up lazily.
@@ -466,4 +480,131 @@
return false;
}
+
+ /**
+ * @param deviceId The input device id of the input device that will handle the shortcuts.
+ *
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts parsed at boot time from {@code bookmarks.xml}.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList();
+ for (int i = 0; i < mIntentShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftShortcuts.size(); i++) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mRoleShortcuts.size(); i++) {
+ String role = mRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ for (int i = 0; i < mShiftRoleShortcuts.size(); i++) {
+ String role = mShiftRoleShortcuts.valueAt(i);
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
+ /**
+ * Given an intent to launch an application and the character and shift state that should
+ * trigger it, return a suitable {@link KeyboardShortcutInfo} that contains the label and
+ * icon for the target application.
+ *
+ * @param baseChar the character that triggers the shortcut
+ * @param intent the application launch intent
+ * @param shift whether the shift key is required to be presed.
+ */
+ @VisibleForTesting
+ KeyboardShortcutInfo shortcutInfoFromIntent(char baseChar, Intent intent, boolean shift) {
+ if (intent == null) {
+ return null;
+ }
+
+ CharSequence label;
+ Icon icon;
+ ActivityInfo resolvedActivity = intent.resolveActivityInfo(
+ mPackageManager, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolvedActivity == null) {
+ return null;
+ }
+ boolean isResolver = com.android.internal.app.ResolverActivity.class.getName().equals(
+ resolvedActivity.name);
+ if (isResolver) {
+ label = getIntentCategoryLabel(mContext,
+ intent.getSelector().getCategories().iterator().next());
+ if (label == null) {
+ return null;
+ }
+ icon = Icon.createWithResource(mContext, R.drawable.sym_def_app_icon);
+
+ } else {
+ label = resolvedActivity.loadLabel(mPackageManager);
+ icon = Icon.createWithResource(
+ resolvedActivity.packageName, resolvedActivity.getIconResource());
+ }
+ int modifiers = KeyEvent.META_META_ON;
+ if (shift) {
+ modifiers |= KeyEvent.META_SHIFT_ON;
+ }
+ return new KeyboardShortcutInfo(label, icon, baseChar, modifiers);
+ }
+
+ @VisibleForTesting
+ static String getIntentCategoryLabel(Context context, CharSequence category) {
+ int resid;
+ switch (category.toString()) {
+ case Intent.CATEGORY_APP_BROWSER:
+ resid = R.string.keyboard_shortcut_group_applications_browser;
+ break;
+ case Intent.CATEGORY_APP_CONTACTS:
+ resid = R.string.keyboard_shortcut_group_applications_contacts;
+ break;
+ case Intent.CATEGORY_APP_EMAIL:
+ resid = R.string.keyboard_shortcut_group_applications_email;
+ break;
+ case Intent.CATEGORY_APP_CALENDAR:
+ resid = R.string.keyboard_shortcut_group_applications_calendar;
+ break;
+ case Intent.CATEGORY_APP_MAPS:
+ resid = R.string.keyboard_shortcut_group_applications_maps;
+ break;
+ case Intent.CATEGORY_APP_MUSIC:
+ resid = R.string.keyboard_shortcut_group_applications_music;
+ break;
+ case Intent.CATEGORY_APP_MESSAGING:
+ resid = R.string.keyboard_shortcut_group_applications_sms;
+ break;
+ case Intent.CATEGORY_APP_CALCULATOR:
+ resid = R.string.keyboard_shortcut_group_applications_calculator;
+ break;
+ default:
+ Log.e(TAG, ("No label for app category " + category));
+ return null;
+ }
+ return context.getString(resid);
+ };
+
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9d0c0e9..8dc9756 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -189,6 +189,7 @@
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.FallbackAction;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -3321,6 +3322,11 @@
eventToLog).sendToTarget();
}
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
+ }
+
// TODO(b/117479243): handle it in InputPolicy
// TODO (b/283241997): Add the remaining keyboard shortcut logging after refactoring
/** {@inheritDoc} */
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9ca4e27..6c05d70 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -82,6 +82,7 @@
import android.view.Display;
import android.view.IDisplayFoldListener;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants;
@@ -698,6 +699,15 @@
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
/**
+ * Return the set of applicaition launch keyboard shortcuts the system supports.
+ *
+ * @param deviceId The id of the {@link InputDevice} that will trigger the shortcut.
+ *
+ * @return {@link KeyboardShortcutGroup} containing the shortcuts.
+ */
+ KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId);
+
+ /**
* Called from the input reader thread before a motion is enqueued when the device is in a
* non-interactive state.
*
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index 6a5a7ac..d34498a 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -47,3 +47,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "add_battery_usage_stats_slice_atom"
+ namespace: "backstage_power"
+ description: "Adds battery_usage_stats_slice atom"
+ bug: "324602949"
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index ddbd809..953aae9 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.trust;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.GrantTrustResult.STATUS_UNLOCKED_BY_GRANT;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
@@ -84,6 +85,9 @@
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
@@ -159,6 +163,7 @@
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
+ private final LockSettingsInternal mLockSettings;
private final LockPatternUtils mLockPatternUtils;
private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
@@ -250,6 +255,20 @@
private final StrongAuthTracker mStrongAuthTracker;
+ // Used to subscribe to device credential auth attempts.
+ private final LockSettingsStateListener mLockSettingsStateListener =
+ new LockSettingsStateListener() {
+ @Override
+ public void onAuthenticationSucceeded(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 1, userId).sendToTarget();
+ }
+
+ @Override
+ public void onAuthenticationFailed(int userId) {
+ mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, 0, userId).sendToTarget();
+ }
+ };
+
private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM;
@@ -294,6 +313,7 @@
mHandler = createHandler(injector.getLooper());
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mLockSettings = LocalServices.getService(LockSettingsInternal.class);
mLockPatternUtils = injector.getLockPatternUtils();
mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
@@ -315,6 +335,9 @@
checkNewAgents();
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ mLockSettings.registerLockSettingsStateListener(mLockSettingsStateListener);
+ }
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
mFaceManager = mContext.getSystemService(FaceManager.class);
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 8768074..3076b87 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7173,7 +7173,7 @@
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawn()
+ ", isAnimationSet=" + isAnimationSet);
if (!w.isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 59b5da8..ff46b33 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4709,6 +4709,10 @@
// Update stored global config and notify everyone about the change.
mRootWindowContainer.onConfigurationChanged(mTempConfig);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+ values.orientation);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changes;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3652c4d..9371149 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4082,12 +4082,12 @@
final Transaction t = mWmService.mTransactionFactory.get();
forAllWindows(w -> {
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController == null) {
+ if (wsa.mSurfaceControl == null) {
return;
}
if (!mWmService.mSessions.contains(wsa.mSession)) {
Slog.w(TAG_WM, "LEAKED SURFACE (session doesn't exist): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mToken
+ " pid=" + w.mSession.mPid
+ " uid=" + w.mSession.mUid);
@@ -4096,7 +4096,7 @@
mTmpWindow = w;
} else if (w.mActivityRecord != null && !w.mActivityRecord.isClientVisible()) {
Slog.w(TAG_WM, "LEAKED SURFACE (app token hidden): "
- + w + " surface=" + wsa.mSurfaceController
+ + w + " surface=" + wsa.mSurfaceControl
+ " token=" + w.mActivityRecord);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE LEAK DESTROY: %s", w);
wsa.destroySurface(t);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 74dbd15..b496a65 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -623,7 +623,7 @@
// occlusion detection depending on the type or if it's a trusted overlay.
populateOverlayInputInfo(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
return;
}
// Skip this window because it cannot possibly receive input.
@@ -687,7 +687,7 @@
if (w.mWinAnimator.hasSurface()) {
populateInputWindowHandle(inputWindowHandle, w);
setInputWindowInfoIfNeeded(mInputTransaction,
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceControl, inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 99697de..f2ccbc4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -667,7 +667,7 @@
boolean reclaimSomeSurfaceMemory(WindowStateAnimator winAnimator, String operation,
boolean secure) {
- final WindowSurfaceController surfaceController = winAnimator.mSurfaceController;
+ final SurfaceControl surfaceControl = winAnimator.mSurfaceControl;
boolean leakedSurface = false;
boolean killedApps = false;
EventLogTags.writeWmNoSurfaceMemory(winAnimator.mWin.toString(),
@@ -692,7 +692,7 @@
return;
}
final WindowStateAnimator wsa = w.mWinAnimator;
- if (wsa.mSurfaceController != null) {
+ if (wsa.mSurfaceControl != null) {
pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
}
}, false /* traverseTopToBottom */);
@@ -717,7 +717,7 @@
// app to request another one.
Slog.w(TAG_WM,
"Looks like we have reclaimed some memory, clearing surface for retry.");
- if (surfaceController != null) {
+ if (surfaceControl != null) {
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
"SURFACE RECOVER DESTROY: %s", winAnimator.mWin);
SurfaceControl.Transaction t = mWmService.mTransactionFactory.get();
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 31fda77..db0374e 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -324,7 +324,7 @@
if (!w.mToken.mRoundedCornerOverlay || !w.isVisible() || !w.mWinAnimator.hasSurface()) {
return;
}
- t.setSkipScreenshot(w.mWinAnimator.mSurfaceController.mSurfaceControl, skipScreenshot);
+ t.setSkipScreenshot(w.mWinAnimator.mSurfaceControl, skipScreenshot);
}, false);
if (!skipScreenshot) {
// Use sync apply to apply the change immediately, so that the next
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 75e3e65..f5108f5b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -109,8 +109,8 @@
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- /** Set of visible alert/app-overlay window surfaces connected to this session. */
- private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
+ /** Set of visible alert/app-overlay windows connected to this session. */
+ private final ArraySet<WindowState> mAlertWindows = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
boolean mCanForceShowingInsets;
@@ -769,9 +769,8 @@
return !mAddedWindows.isEmpty();
}
- void onWindowSurfaceVisibilityChanged(WindowSurfaceController surfaceController,
- boolean visible, int type) {
-
+ void onWindowSurfaceVisibilityChanged(WindowState window, boolean visible) {
+ final int type = window.mAttrs.type;
if (!isSystemAlertWindowType(type)) {
return;
}
@@ -782,7 +781,7 @@
final boolean noSystemOverlayPermission =
!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
+ changed = mAlertWindows.add(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -791,7 +790,7 @@
true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
} else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
+ changed = mAlertWindows.remove(window);
if (type == TYPE_APPLICATION_OVERLAY) {
MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
@@ -802,7 +801,7 @@
}
if (changed && noSystemOverlayPermission) {
- if (mAlertWindowSurfaces.isEmpty()) {
+ if (mAlertWindows.isEmpty()) {
cancelAlertWindowNotification();
} else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) {
mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
@@ -815,7 +814,7 @@
if (changed && mPid != WindowManagerService.MY_PID) {
// Notify activity manager that the process contains overlay/alert windows, so it can
// adjust the importance score for the process.
- setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
+ setHasOverlayUi(!mAlertWindows.isEmpty());
}
}
@@ -859,7 +858,7 @@
}
mSurfaceSession = null;
mAddedWindows.clear();
- mAlertWindowSurfaces.clear();
+ mAlertWindows.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
@@ -880,7 +879,7 @@
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
+ pw.print(" mAlertWindows="); pw.print(mAlertWindows);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName);
@@ -896,9 +895,9 @@
/** @return {@code true} if there is an alert window surface on the given display. */
boolean hasAlertWindowSurfaces(DisplayContent displayContent) {
- for (int i = mAlertWindowSurfaces.size() - 1; i >= 0; i--) {
- final WindowSurfaceController surfaceController = mAlertWindowSurfaces.valueAt(i);
- if (surfaceController.mAnimator.mWin.getDisplayContent() == displayContent) {
+ for (int i = mAlertWindows.size() - 1; i >= 0; i--) {
+ final WindowState window = mAlertWindows.valueAt(i);
+ if (window.mDisplayContent == displayContent) {
return true;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8033122..ebbf6e3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -283,6 +283,7 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.MagnificationSpec;
import android.view.RemoteAnimationAdapter;
import android.view.ScrollCaptureResponse;
@@ -334,8 +335,8 @@
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.protolog.LegacyProtoLogImpl;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
@@ -2569,7 +2570,7 @@
// surface, let the client use that, but don't create new surface at this
// point.
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
+ winAnimator.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} else {
if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
@@ -2765,15 +2766,15 @@
result |= RELAYOUT_RES_SURFACE_CHANGED;
}
- WindowSurfaceController surfaceController;
+ SurfaceControl surfaceControl;
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
- surfaceController = winAnimator.createSurfaceLocked();
+ surfaceControl = winAnimator.createSurfaceLocked();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- if (surfaceController != null) {
- surfaceController.getSurfaceControl(outSurfaceControl);
+ if (surfaceControl != null) {
+ winAnimator.getSurfaceControl(outSurfaceControl);
ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);
} else {
@@ -6772,11 +6773,11 @@
if (windowState == null) {
return false;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return false;
}
- return surfaceController.clearWindowContentFrameStats();
+ return surfaceControl.clearContentFrameStats();
}
}
@@ -6791,15 +6792,15 @@
if (windowState == null) {
return null;
}
- WindowSurfaceController surfaceController = windowState.mWinAnimator.mSurfaceController;
- if (surfaceController == null) {
+ final SurfaceControl surfaceControl = windowState.mWinAnimator.mSurfaceControl;
+ if (surfaceControl == null) {
return null;
}
if (mTempWindowRenderStats == null) {
mTempWindowRenderStats = new WindowContentFrameStats();
}
WindowContentFrameStats stats = mTempWindowRenderStats;
- if (!surfaceController.getWindowContentFrameStats(stats)) {
+ if (!surfaceControl.getContentFrameStats(stats)) {
return null;
}
return stats;
@@ -7440,6 +7441,16 @@
}
@Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPolicy.getApplicationLaunchKeyboardShortcuts(deviceId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 228eb76..9ebb89d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -98,6 +98,7 @@
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static com.android.input.flags.Flags.removeInputChannelFromWindowstate;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -633,6 +634,9 @@
// Input channel and input window handle used by the input dispatcher.
final InputWindowHandleWrapper mInputWindowHandle;
+ /**
+ * Only populated if flag REMOVE_INPUT_CHANNEL_FROM_WINDOWSTATE is disabled.
+ */
InputChannel mInputChannel;
/**
@@ -1497,7 +1501,7 @@
if (isDrawn()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceController);
+ winAnimator.mSurfaceControl);
setOrientationChanging(false);
mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- mWmService.mDisplayFreezeTime);
@@ -1877,6 +1881,10 @@
* Input Manager uses when discarding windows from input consideration.
*/
boolean isPotentialDragTarget(boolean targetInterceptsGlobalDrag) {
+ if (removeInputChannelFromWindowstate()) {
+ return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
+ && mInputChannelToken != null && mInputWindowHandle != null;
+ }
return (targetInterceptsGlobalDrag || isVisibleNow()) && !mRemoved
&& mInputChannel != null && mInputWindowHandle != null;
}
@@ -2417,7 +2425,7 @@
ProtoLog.v(WM_DEBUG_FOCUS, "Remove client=%x, surfaceController=%s Callers=%s",
System.identityHashCode(mClient.asBinder()),
- mWinAnimator.mSurfaceController,
+ mWinAnimator.mSurfaceControl,
Debug.getCallers(5));
final DisplayContent displayContent = getDisplayContent();
@@ -2428,10 +2436,10 @@
mOnBackInvokedCallbackInfo = null;
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "Remove %s: mSurfaceController=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "Remove %s: mSurfaceControl=%s mAnimatingExit=%b mRemoveOnExit=%b "
+ "mHasSurface=%b surfaceShowing=%b animating=%b app-animation=%b "
+ "mDisplayFrozen=%b callers=%s",
- this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
+ this, mWinAnimator.mSurfaceControl, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
@@ -2608,7 +2616,7 @@
+ isVisibleRequestedOrAdding() + " isVisible: " + (isVisible()
&& mActivityRecord != null && mActivityRecord.isVisible()));
if (!isVisibleRequestedOrAdding()) {
- Slog.i(TAG_WM, " mSurfaceController=" + mWinAnimator.mSurfaceController
+ Slog.i(TAG_WM, " mSurfaceControl=" + mWinAnimator.mSurfaceControl
+ " relayoutCalled=" + mRelayoutCalled
+ " viewVis=" + mViewVisibility
+ " policyVis=" + isVisibleByPolicy()
@@ -2626,6 +2634,19 @@
}
void openInputChannel(@NonNull InputChannel outInputChannel) {
+ if (mInputChannelToken != null) {
+ throw new IllegalStateException("Window already has an input channel token.");
+ }
+ if (removeInputChannelFromWindowstate()) {
+ String name = getName();
+ InputChannel channel = mWmService.mInputManager.createInputChannel(name);
+ mInputChannelToken = channel.getToken();
+ mInputWindowHandle.setToken(mInputChannelToken);
+ mWmService.mInputToWindowMap.put(mInputChannelToken, this);
+ channel.copyTo(outInputChannel);
+ channel.dispose();
+ return;
+ }
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
@@ -2657,9 +2678,11 @@
mInputChannelToken = null;
}
- if (mInputChannel != null) {
- mInputChannel.dispose();
- mInputChannel = null;
+ if (!removeInputChannelFromWindowstate()) {
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
mInputWindowHandle.setToken(null);
}
@@ -4434,7 +4457,7 @@
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState c = mChildren.get(i);
- if (c.mWinAnimator.mSurfaceController != null) {
+ if (c.mWinAnimator.mSurfaceControl != null) {
c.performShowLocked();
// It hadn't been shown, which means layout not performed on it, so now we
// want to make sure to do a layout. If called from within the transaction
@@ -4891,7 +4914,7 @@
Slog.v(TAG, "Win " + this + ": isDrawn=" + isDrawn()
+ ", animating=" + isAnimating(TRANSITION | PARENTS));
if (!isDrawn()) {
- Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceController
+ Slog.v(TAG, "Not displayed: s=" + mWinAnimator.mSurfaceControl
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
@@ -5512,13 +5535,13 @@
// been defined and so we can use static layers and leave it that way.
if (w.mAttrs.type == TYPE_APPLICATION_MEDIA) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -2);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -2);
} else {
w.assignLayer(t, -2);
}
} else if (w.mAttrs.type == TYPE_APPLICATION_MEDIA_OVERLAY) {
if (mWinAnimator.hasSurface()) {
- w.assignRelativeLayer(t, mWinAnimator.mSurfaceController.mSurfaceControl, -1);
+ w.assignRelativeLayer(t, mWinAnimator.mSurfaceControl, -1);
} else {
w.assignLayer(t, -1);
}
@@ -5694,7 +5717,7 @@
}
SurfaceControl getClientViewRootSurface() {
- return mWinAnimator.getSurfaceControl();
+ return mWinAnimator.mSurfaceControl;
}
/** Drops a buffer for this window's view-root from a transaction */
@@ -6094,11 +6117,10 @@
}
getPendingTransaction().setSecure(mSurfaceControl, isSecure);
} else {
- if (mWinAnimator.mSurfaceController == null
- || mWinAnimator.mSurfaceController.mSurfaceControl == null) {
+ if (mWinAnimator.mSurfaceControl == null) {
return;
}
- getPendingTransaction().setSecure(mWinAnimator.mSurfaceController.mSurfaceControl,
+ getPendingTransaction().setSecure(mWinAnimator.mSurfaceControl,
isSecure);
}
if (mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 397a6357..24a2a62 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.SurfaceControl.METADATA_OWNER_PID;
+import static android.view.SurfaceControl.METADATA_OWNER_UID;
+import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -44,6 +48,7 @@
import static com.android.server.wm.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.WindowStateAnimatorProto.SURFACE;
import static com.android.server.wm.WindowStateAnimatorProto.SYSTEM_DECOR_RECT;
+import static com.android.server.wm.WindowSurfaceControllerProto.SHOWN;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.setScPropertiesInClient;
@@ -52,6 +57,7 @@
import android.graphics.Rect;
import android.os.Debug;
import android.os.Trace;
+import android.util.EventLog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -95,12 +101,13 @@
final Session mSession;
final WindowManagerPolicy mPolicy;
final Context mContext;
- final boolean mIsWallpaper;
private final WallpaperController mWallpaperControllerLocked;
boolean mAnimationIsEntrance;
- WindowSurfaceController mSurfaceController;
+ SurfaceControl mSurfaceControl;
+ private boolean mSurfaceShown;
+ private String mTitle;
float mShownAlpha = 0;
float mAlpha = 0;
@@ -164,7 +171,6 @@
mWin = win;
mSession = win.mSession;
mAttrType = win.mAttrs.type;
- mIsWallpaper = win.mIsWallpaper;
mWallpaperControllerLocked = win.getDisplayContent().mWallpaperController;
}
@@ -198,14 +204,30 @@
}
void hide(SurfaceControl.Transaction transaction, String reason) {
- if (!mLastHidden) {
- //dump();
- mLastHidden = true;
-
- if (mSurfaceController != null) {
- mSurfaceController.hide(transaction, reason);
- }
+ if (mLastHidden) {
+ return;
}
+ mLastHidden = true;
+ if (mSurfaceControl == null || !mSurfaceShown) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, mTitle);
+
+ setShown(false);
+ transaction.hide(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.getDisplayContent();
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ private void setShown(boolean surfaceShown) {
+ mSurfaceShown = surfaceShown;
+ mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mWin, surfaceShown);
+ mWin.onSurfaceShownChanged(surfaceShown);
+ mSession.onWindowSurfaceVisibilityChanged(mWin, mSurfaceShown);
}
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction) {
@@ -221,7 +243,7 @@
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
- mSurfaceController);
+ mSurfaceControl);
if (startingWindow) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
@@ -248,7 +270,7 @@
return false;
}
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
- mSurfaceController);
+ mSurfaceControl);
mDrawState = READY_TO_SHOW;
boolean result = false;
final ActivityRecord activity = mWin.mActivityRecord;
@@ -271,11 +293,11 @@
}
}
- WindowSurfaceController createSurfaceLocked() {
+ SurfaceControl createSurfaceLocked() {
final WindowState w = mWin;
- if (mSurfaceController != null) {
- return mSurfaceController;
+ if (mSurfaceControl != null) {
+ return mSurfaceControl;
}
w.setHasSurface(false);
@@ -312,10 +334,22 @@
final boolean isHwAccelerated = (attrs.flags & FLAG_HARDWARE_ACCELERATED) != 0;
final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
- mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
- flags, this, attrs.type);
+ mTitle = attrs.getTitle().toString();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+ mSurfaceControl = mWin.makeSurface()
+ .setParent(mWin.mSurfaceControl)
+ .setName(mTitle)
+ .setFormat(format)
+ .setFlags(flags)
+ .setMetadata(METADATA_WINDOW_TYPE, attrs.type)
+ .setMetadata(METADATA_OWNER_UID, mSession.mUid)
+ .setMetadata(METADATA_OWNER_PID, mSession.mPid)
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
if (!setScPropertiesInClient()) {
- mSurfaceController.setColorSpaceAgnostic(w.getPendingTransaction(),
+ setColorSpaceAgnosticLocked(
(attrs.privateFlags & LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
}
@@ -326,7 +360,7 @@
ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
" CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
- mSurfaceController, mSession.mSurfaceSession, mSession.mPid, attrs.format,
+ mSurfaceControl, mSession.mSurfaceSession, mSession.mPid, attrs.format,
flags, this);
} catch (OutOfResourcesException e) {
Slog.w(TAG, "OutOfResourcesException creating surface");
@@ -340,7 +374,7 @@
}
if (DEBUG) {
- Slog.v(TAG, "Got surface: " + mSurfaceController
+ Slog.v(TAG, "Got surface: " + mSurfaceControl
+ ", set left=" + w.getFrame().left + " top=" + w.getFrame().top);
}
@@ -353,15 +387,19 @@
mLastHidden = true;
if (DEBUG) Slog.v(TAG, "Created surface " + this);
- return mSurfaceController;
+ return mSurfaceControl;
}
boolean hasSurface() {
- return mSurfaceController != null && mSurfaceController.hasSurface();
+ return mSurfaceControl != null;
+ }
+
+ void getSurfaceControl(SurfaceControl outSurfaceControl) {
+ outSurfaceControl.copyFrom(mSurfaceControl, "WindowStateAnimator.getSurfaceControl");
}
void destroySurfaceLocked(SurfaceControl.Transaction t) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
@@ -370,7 +408,7 @@
try {
if (DEBUG_VISIBILITY) {
logWithStack(TAG, "Window " + this + " destroying surface "
- + mSurfaceController + ", session " + mSession);
+ + mSurfaceControl + ", session " + mSession);
}
ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s",
mWin, new RuntimeException().fillInStackTrace());
@@ -384,23 +422,13 @@
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception thrown when destroying Window " + this
- + " surface " + mSurfaceController + " session " + mSession + ": "
+ + " surface " + mSurfaceControl + " session " + mSession + ": "
+ e.toString());
}
-
- // Whether the surface was preserved (and copied to mPendingDestroySurface) or not, it
- // needs to be cleared to match the WindowState.mHasSurface state. It is also necessary
- // so it can be recreated successfully in mPendingDestroySurface case.
- mWin.setHasSurface(false);
- if (mSurfaceController != null) {
- mSurfaceController.setShown(false);
- }
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
}
void computeShownFrameLocked() {
- if (mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
+ if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
return;
} else if (mWin.isDragResizeChanged()) {
// This window is awaiting a relayout because user just started (or ended)
@@ -454,14 +482,13 @@
mLastAlpha = mShownAlpha;
ProtoLog.i(WM_SHOW_TRANSACTIONS,
"SURFACE controller=%s alpha=%f HScale=%f, VScale=%f: %s",
- mSurfaceController, mShownAlpha, w.mHScale, w.mVScale, w);
+ mSurfaceControl, mShownAlpha, w.mHScale, w.mVScale, w);
- boolean prepared =
- mSurfaceController.prepareToShowInTransaction(t, mShownAlpha);
+ t.setAlpha(mSurfaceControl, mShownAlpha);
- if (prepared && mDrawState == HAS_DRAWN) {
+ if (mDrawState == HAS_DRAWN) {
if (mLastHidden) {
- mSurfaceController.showRobustly(t);
+ showRobustly(t);
mLastHidden = false;
final DisplayContent displayContent = w.getDisplayContent();
if (!displayContent.getLastHasContent()) {
@@ -494,18 +521,38 @@
}
}
- void setOpaqueLocked(boolean isOpaque) {
- if (mSurfaceController == null) {
+ private void showRobustly(SurfaceControl.Transaction t) {
+ if (mSurfaceShown) {
return;
}
- mSurfaceController.setOpaque(isOpaque);
+
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", mTitle);
+ if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during relayout");
+ setShown(true);
+ t.show(mSurfaceControl);
+ if (mWin.mIsWallpaper) {
+ final DisplayContent dc = mWin.mDisplayContent;
+ EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
+ }
+ }
+
+ void setOpaqueLocked(boolean isOpaque) {
+ if (mSurfaceControl == null) {
+ return;
+ }
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, mTitle);
+ mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
+ mService.scheduleAnimationLocked();
}
void setColorSpaceAgnosticLocked(boolean agnostic) {
- if (mSurfaceController == null) {
+ if (mSurfaceControl == null) {
return;
}
- mSurfaceController.setColorSpaceAgnostic(mWin.getPendingTransaction(), agnostic);
+ ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, mTitle);
+ mWin.getPendingTransaction().setColorSpaceAgnostic(mSurfaceControl, agnostic);
}
void applyEnterAnimationLocked() {
@@ -521,7 +568,7 @@
// should be controlled by ActivityRecord in general. Wallpaper is also excluded because
// WallpaperController should handle it. Also skip play enter animation for the window
// below starting window.
- if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper
+ if (mAttrType != TYPE_BASE_APPLICATION && !mWin.mIsWallpaper
&& !(mWin.mActivityRecord != null && mWin.mActivityRecord.hasStartingWindow())) {
applyAnimationLocked(transit, true);
}
@@ -614,8 +661,10 @@
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- if (mSurfaceController != null) {
- mSurfaceController.dumpDebug(proto, SURFACE);
+ if (mSurfaceControl != null) {
+ final long dumpToken = proto.start(SURFACE);
+ proto.write(SHOWN, mSurfaceShown);
+ proto.end(dumpToken);
}
proto.write(DRAW_STATE, mDrawState);
mSystemDecorRect.dumpDebug(proto, SYSTEM_DECOR_RECT);
@@ -626,8 +675,11 @@
if (mAnimationIsEntrance) {
pw.print(prefix); pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
}
- if (mSurfaceController != null) {
- mSurfaceController.dump(pw, prefix, dumpAll);
+ if (mSurfaceControl != null) {
+ if (dumpAll) {
+ pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+ }
+ pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
}
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
@@ -659,31 +711,24 @@
}
boolean getShown() {
- if (mSurfaceController != null) {
- return mSurfaceController.getShown();
- }
- return false;
+ return mSurfaceControl != null && mSurfaceShown;
}
void destroySurface(SurfaceControl.Transaction t) {
- try {
- if (mSurfaceController != null) {
- mSurfaceController.destroy(t);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception thrown when destroying surface " + this
- + " surface " + mSurfaceController + " session " + mSession + ": " + e);
- } finally {
- mWin.setHasSurface(false);
- mSurfaceController = null;
- mDrawState = NO_SURFACE;
+ if (mSurfaceControl == null) {
+ return;
}
- }
-
- SurfaceControl getSurfaceControl() {
- if (!hasSurface()) {
- return null;
+ ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
+ "Destroying surface %s called by %s", this, Debug.getCallers(8));
+ if (mWin.mIsWallpaper && !mWin.mWindowRemovalAllowed && !mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mWin
+ + " by " + Debug.getCallers(8));
}
- return mSurfaceController.mSurfaceControl;
+ t.remove(mSurfaceControl);
+ setShown(false);
+ mSurfaceControl = null;
+ mWin.setHasSurface(false);
+ mDrawState = NO_SURFACE;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
deleted file mode 100644
index d9766e0..0000000
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.SurfaceControl.METADATA_OWNER_PID;
-import static android.view.SurfaceControl.METADATA_OWNER_UID;
-import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
-
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
-import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
-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.WindowSurfaceControllerProto.SHOWN;
-
-import android.os.Debug;
-import android.os.Trace;
-import android.util.EventLog;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl;
-import android.view.WindowContentFrameStats;
-
-import com.android.internal.protolog.ProtoLog;
-
-import java.io.PrintWriter;
-
-class WindowSurfaceController {
- static final String TAG = TAG_WITH_CLASS_NAME ? "WindowSurfaceController" : TAG_WM;
-
- final WindowStateAnimator mAnimator;
-
- SurfaceControl mSurfaceControl;
-
- // Should only be set from within setShown().
- private boolean mSurfaceShown = false;
-
- private final String title;
-
- private final WindowManagerService mService;
-
- private final int mWindowType;
- private final Session mWindowSession;
-
-
- WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
- int windowType) {
- mAnimator = animator;
-
- title = name;
-
- mService = animator.mService;
- final WindowState win = animator.mWin;
- mWindowType = windowType;
- mWindowSession = win.mSession;
-
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- mSurfaceControl = win.makeSurface()
- .setParent(win.getSurfaceControl())
- .setName(name)
- .setFormat(format)
- .setFlags(flags)
- .setMetadata(METADATA_WINDOW_TYPE, windowType)
- .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
- .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController")
- .setBLASTLayer().build();
-
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
-
- void hide(SurfaceControl.Transaction transaction, String reason) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE HIDE ( %s ): %s", reason, title);
-
- if (mSurfaceShown) {
- hideSurface(transaction);
- }
- }
-
- private void hideSurface(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
- setShown(false);
- try {
- transaction.hide(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 0 /* request hidden */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Exception hiding surface in " + this);
- }
- }
-
- void destroy(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
- "Destroying surface %s called by %s", this, Debug.getCallers(8));
- try {
- if (mSurfaceControl != null) {
- if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
- && !mAnimator.mWin.mRemoveOnExit) {
- // The wallpaper surface should have the same lifetime as its window.
- Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
- + " by " + Debug.getCallers(8));
- }
- t.remove(mSurfaceControl);
- }
- } catch (RuntimeException e) {
- Slog.w(TAG, "Error destroying surface in: " + this, e);
- } finally {
- setShown(false);
- mSurfaceControl = null;
- }
- }
-
- boolean prepareToShowInTransaction(SurfaceControl.Transaction t, float alpha) {
- if (mSurfaceControl == null) {
- return false;
- }
-
- t.setAlpha(mSurfaceControl, alpha);
- return true;
- }
-
- void setOpaque(boolean isOpaque) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title);
-
- if (mSurfaceControl == null) {
- return;
- }
-
- mAnimator.mWin.getPendingTransaction().setOpaque(mSurfaceControl, isOpaque);
- mService.scheduleAnimationLocked();
- }
-
- void setColorSpaceAgnostic(SurfaceControl.Transaction t, boolean agnostic) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isColorSpaceAgnostic=%b: %s", agnostic, title);
-
- if (mSurfaceControl == null) {
- return;
- }
- t.setColorSpaceAgnostic(mSurfaceControl, agnostic);
- }
-
- void showRobustly(SurfaceControl.Transaction t) {
- ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this
- + " during relayout");
-
- if (mSurfaceShown) {
- return;
- }
-
- setShown(true);
- t.show(mSurfaceControl);
- if (mAnimator.mIsWallpaper) {
- final DisplayContent dc = mAnimator.mWin.getDisplayContent();
- EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- dc.mDisplayId, 1 /* request shown */,
- String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
- }
- }
-
- boolean clearWindowContentFrameStats() {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.clearContentFrameStats();
- }
-
- boolean getWindowContentFrameStats(WindowContentFrameStats outStats) {
- if (mSurfaceControl == null) {
- return false;
- }
- return mSurfaceControl.getContentFrameStats(outStats);
- }
-
- boolean hasSurface() {
- return mSurfaceControl != null;
- }
-
- void getSurfaceControl(SurfaceControl outSurfaceControl) {
- outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
- }
-
- boolean getShown() {
- return mSurfaceShown;
- }
-
- void setShown(boolean surfaceShown) {
- mSurfaceShown = surfaceShown;
-
- mService.updateNonSystemOverlayWindowsVisibilityIfNeeded(mAnimator.mWin, surfaceShown);
-
- mAnimator.mWin.onSurfaceShownChanged(surfaceShown);
-
- if (mWindowSession != null) {
- mWindowSession.onWindowSurfaceVisibilityChanged(this, mSurfaceShown, mWindowType);
- }
- }
-
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- final long token = proto.start(fieldId);
- proto.write(SHOWN, mSurfaceShown);
- proto.end(token);
- }
-
- public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
- }
- pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
- }
-
- @Override
- public String toString() {
- return mSurfaceControl.toString();
- }
-}
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/manifest_services.xml b/services/manifest_services.xml
index 8ff1a7d..114fe32 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,9 +4,4 @@
<version>2</version>
<fqname>IAltitudeService/default</fqname>
</hal>
- <hal format="aidl">
- <name>android.frameworks.vibrator</name>
- <version>1</version>
- <fqname>IVibratorControlService/default</fqname>
- </hal>
</manifest>
diff --git a/services/manifest_services_android.frameworks.vibrator.xml b/services/manifest_services_android.frameworks.vibrator.xml
new file mode 100644
index 0000000..c287643
--- /dev/null
+++ b/services/manifest_services_android.frameworks.vibrator.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.frameworks.vibrator</name>
+ <version>1</version>
+ <fqname>IVibratorControlService/default</fqname>
+ </hal>
+</manifest>
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 3d40f64..927146d 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -21,6 +21,7 @@
":services.net-sources",
],
static_libs: [
+ "modules-utils-build_system",
"netd-client",
"networkstack-client",
"net-utils-services-common",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index f690b1b..2d4a29b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -18,6 +18,8 @@
import static org.junit.Assert.assertEquals;
+import android.hardware.display.BrightnessInfo;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -112,7 +114,10 @@
.append("\n mBrightnessAdjustmentFlag:")
.append(displayBrightnessState.getBrightnessAdjustmentFlag())
.append("\n mIsUserInitiatedChange:")
- .append(displayBrightnessState.isUserInitiatedChange());
+ .append(displayBrightnessState.isUserInitiatedChange())
+ .append("\n mBrightnessMaxReason:")
+ .append(BrightnessInfo.briMaxReasonToString(
+ displayBrightnessState.getBrightnessMaxReason()));
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index e982153..93dfbcb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -30,7 +30,6 @@
import android.content.Context;
import android.hardware.SensorManager;
-import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal;
import android.os.Handler;
import android.os.PowerManager;
@@ -155,12 +154,6 @@
}
@Test
- public void testMaxReasonIsNoneOnInit() {
- assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
- mClamperController.getBrightnessMaxReason());
- }
-
- @Test
public void testOnDisplayChanged_DelegatesToClamper() {
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 4149e44..5b2c0c6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -53,6 +53,7 @@
"mockingservicestests-utils-mockito",
"mockito-target-extended-minus-junit4",
"platform-compat-test-rules",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"PlatformProperties",
"service-blobstore",
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 00daf41..1a398c5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.trust;
+import static android.security.Flags.FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH;
+import static android.security.Flags.shouldTrustManagerListenForPrimaryAuth;
import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
@@ -73,6 +75,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
@@ -91,6 +95,7 @@
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockSettingsStateListener;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -101,6 +106,7 @@
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -112,7 +118,16 @@
import java.util.List;
import java.util.Map;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+@RunWith(ParameterizedAndroidJunit4.class)
public class TrustManagerServiceTest {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_SHOULD_TRUST_MANAGER_LISTEN_FOR_PRIMARY_AUTH);
+ }
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -122,6 +137,9 @@
.build();
@Rule
+ public final SetFlagsRule mSetFlagsRule;
+
+ @Rule
public final MockContext mMockContext = new MockContext(
ApplicationProvider.getApplicationContext());
@@ -162,6 +180,10 @@
private ITrustManager mTrustManager;
private ActivityManagerInternal mPreviousActivityManagerInternal;
+ public TrustManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT, flags);
+ }
+
@Before
public void setUp() throws Exception {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
@@ -594,11 +616,27 @@
}
private void attemptSuccessfulUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationSucceeded(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ }
}
private void attemptFailedUnlock(int userId) throws RemoteException {
- mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ if (shouldTrustManagerListenForPrimaryAuth()) {
+ ArgumentCaptor<LockSettingsStateListener> captor =
+ ArgumentCaptor.forClass(LockSettingsStateListener.class);
+ verify(mLockSettingsInternal).registerLockSettingsStateListener(captor.capture());
+ LockSettingsStateListener listener = captor.getValue();
+ listener.onAuthenticationFailed(userId);
+ } else {
+ mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ }
}
private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
index 8863d27..41cb6fd 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEventLoggerTest.java
@@ -18,11 +18,15 @@
import static com.google.common.truth.Truth.assertThat;
+import android.chre.flags.Flags;
import android.hardware.location.NanoAppMessage;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,8 @@
public class ContextHubEventLoggerTest {
private static final ContextHubEventLogger sInstance = ContextHubEventLogger.getInstance();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
public void testLogNanoappLoad() {
ContextHubEventLogger.NanoappLoadEvent[] events =
@@ -46,10 +52,10 @@
sInstance.clear();
sInstance.logNanoappLoad(-1, 42, -34, 100, false);
sInstance.logNanoappLoad(0, 123, 321, 001, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -66,10 +72,10 @@
sInstance.clear();
sInstance.logNanoappUnload(-1, 47, false);
sInstance.logNanoappUnload(1, 0xFFFFFFFF, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -90,10 +96,10 @@
sInstance.clear();
sInstance.logMessageFromNanoapp(-123, message1, false);
sInstance.logMessageFromNanoapp(321, message2, true);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -114,10 +120,54 @@
sInstance.clear();
sInstance.logMessageToNanoapp(888, message1, true);
sInstance.logMessageToNanoapp(999, message2, false);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_RELIABLE_MESSAGE,
+ Flags.FLAG_RELIABLE_MESSAGE_IMPLEMENTATION})
+ public void testLogReliableMessageToNanoappStatus() {
+ NanoAppMessage message1 = NanoAppMessage.createMessageToNanoApp(1, 0,
+ new byte[] {0x00, 0x11, 0x22, 0x33});
+ NanoAppMessage message2 = NanoAppMessage.createMessageToNanoApp(0, 1,
+ new byte[] {(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF});
+ message1.setIsReliable(true);
+ message2.setIsReliable(true);
+ message1.setMessageSequenceNumber(0);
+ message2.setMessageSequenceNumber(1);
+
+ ContextHubEventLogger.NanoappMessageEvent[] events =
+ new ContextHubEventLogger.NanoappMessageEvent[] {
+ new ContextHubEventLogger.NanoappMessageEvent(23, 888, message1, true),
+ new ContextHubEventLogger.NanoappMessageEvent(34, 999, message2, false)
+ };
+ String[] eventStrings = generateEventDumpStrings(events);
+
+ // log events and test sInstance.toString() contains event details
+ sInstance.clear();
+ sInstance.logMessageToNanoapp(888, message1, true);
+ sInstance.logMessageToNanoapp(999, message2, false);
+ String instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
+ }
+
+ // set the error codes for the events and verify
+ sInstance.logReliableMessageToNanoappStatus(0, (byte) 0x02);
+ sInstance.logReliableMessageToNanoappStatus(1, (byte) 0x03);
+ events[0].setErrorCode((byte) 0x02);
+ events[1].setErrorCode((byte) 0x03);
+ eventStrings = generateEventDumpStrings(events);
+
+ instanceDump = sInstance.toString();
+ for (String eventString: eventStrings) {
+ assertThat(eventString.length() > 0).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
@@ -134,10 +184,10 @@
sInstance.clear();
sInstance.logContextHubRestart(1);
sInstance.logContextHubRestart(2);
- String sInstanceDump = sInstance.toString();
+ String instanceDump = sInstance.toString();
for (String eventString: eventStrings) {
assertThat(eventString.length() > 0).isTrue();
- assertThat(sInstanceDump.contains(eventString)).isTrue();
+ assertThat(instanceDump.contains(eventString)).isTrue();
}
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 777f618..6e6b70d 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -52,6 +52,7 @@
<uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
<uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS"/>
<uses-permission android:name="android.permission.DUMP"/>
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
new file mode 100644
index 0000000..88419e9
--- /dev/null
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<bookmarks>
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+</bookmarks>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
new file mode 100644
index 0000000..8c375d4
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutManagerTests.java
@@ -0,0 +1,167 @@
+/*
+ * 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.policy;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+
+/**
+ * Test class for {@link ModifierShortcutManager}.
+ *
+ * Build/Install/Run:
+ * atest ModifierShortcutManagerTests
+ */
+
+@SmallTest
+public class ModifierShortcutManagerTests {
+ private ModifierShortcutManager mModifierShortcutManager;
+ private Handler mHandler;
+ private Context mContext;
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ mHandler = new Handler(Looper.getMainLooper());
+ mContext = spy(getInstrumentation().getTargetContext());
+ mResources = spy(mContext.getResources());
+
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks);
+
+ mModifierShortcutManager = new ModifierShortcutManager(mContext, mHandler);
+ }
+
+ @Test
+ public void test_getApplicationLaunchKeyboardShortcuts() {
+ KeyboardShortcutGroup group =
+ mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(-1);
+ assertEquals(8, group.getItems().size());
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_appIntent() {
+ Intent mockIntent = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ when(mockActivityInfo.loadLabel(anyObject())).thenReturn("label");
+ mockActivityInfo.packageName = "android";
+ when(mockActivityInfo.getIconResource()).thenReturn(R.drawable.sym_def_app_icon);
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, true);
+
+ assertEquals("label", info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON, info.getModifiers());
+
+ }
+
+ @Test
+ public void test_shortcutInfoFromIntent_resolverIntent() {
+ Intent mockIntent = mock(Intent.class);
+ Intent mockSelector = mock(Intent.class);
+ ActivityInfo mockActivityInfo = mock(ActivityInfo.class);
+ mockActivityInfo.name = com.android.internal.app.ResolverActivity.class.getName();
+ when(mockIntent.resolveActivityInfo(anyObject(), anyInt())).thenReturn(mockActivityInfo);
+ when(mockIntent.getSelector()).thenReturn(mockSelector);
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton(Intent.CATEGORY_APP_BROWSER));
+
+ KeyboardShortcutInfo info = mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false);
+
+ assertEquals(mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ info.getLabel().toString());
+ assertEquals('a', info.getBaseCharacter());
+ assertEquals(R.drawable.sym_def_app_icon, info.getIcon().getResId());
+ assertEquals(KeyEvent.META_META_ON, info.getModifiers());
+
+ // validate that an unknown category that we can't present a label to the user for
+ // returns null shortcut info.
+ when(mockSelector.getCategories()).thenReturn(
+ Collections.singleton("not_a_category"));
+ assertEquals(null, mModifierShortcutManager.shortcutInfoFromIntent(
+ 'a', mockIntent, false));
+ }
+
+ @Test
+ public void test_getIntentCategoryLabel() {
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_browser),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_BROWSER));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_contacts),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CONTACTS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_email),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_EMAIL));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALENDAR));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_maps),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MAPS));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_music),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MUSIC));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_sms),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_MESSAGING));
+ assertEquals(
+ mContext.getString(R.string.keyboard_shortcut_group_applications_calculator),
+ ModifierShortcutManager.getIntentCategoryLabel(
+ mContext, Intent.CATEGORY_APP_CALCULATOR));
+ assertEquals(null, ModifierShortcutManager.getIntentCategoryLabel(mContext, "foo"));
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 0ed02dd..59b08a5 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -65,7 +65,8 @@
private static final SparseArray<String> INTENT_SHORTCUTS = new SparseArray<>();
private static final SparseArray<String> ROLE_SHORTCUTS = new SparseArray<>();
static {
- // These shortcuts should align with those defined in bookmarks.xml
+ // These shortcuts should align with those defined in
+ // services/tests/wmtests/res/xml/bookmarks.xml
INTENT_SHORTCUTS.append(KEYCODE_U, Intent.CATEGORY_APP_CALCULATOR);
INTENT_SHORTCUTS.append(KEYCODE_C, Intent.CATEGORY_APP_CONTACTS);
INTENT_SHORTCUTS.append(KEYCODE_E, Intent.CATEGORY_APP_EMAIL);
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index dff4984..f5c8fb8 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -49,6 +49,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -102,6 +103,9 @@
doReturn(mResources).when(mContext).getResources();
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
+ XmlResourceParser testBookmarks = mResources.getXml(
+ com.android.frameworks.wmtests.R.xml.bookmarks);
+ doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c1be5ca..63e3e5c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -714,7 +714,7 @@
// Simulate when the window is exiting and cleanupAnimation invoked
// (e.g. screen off during RecentsAnimation animating), will expect the window receives
// onExitAnimationDone to destroy the surface when the removal is allowed.
- win1.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
+ win1.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win1.mHasSurface = true;
win1.mAnimatingExit = true;
win1.mRemoveOnExit = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 11d9629..0bf27d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,8 +43,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -758,12 +756,9 @@
// Simulating win1 has shown IME and being IME layering/input target
mDisplayContent.setImeLayeringTarget(win1);
mDisplayContent.setImeInputTarget(win1);
- mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class);
mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test");
spyOn(mDisplayContent);
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface();
- doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController)
- .prepareToShowInTransaction(any(), anyFloat());
+ mImeWindow.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
makeWindowVisibleAndDrawn(mImeWindow);
assertTrue(mImeWindow.isOnScreen());
assertFalse(mImeWindow.isParentWindowHidden());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 00a8842..38ad9a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -27,6 +27,7 @@
import android.os.PowerManager.WakeReason;
import android.util.proto.ProtoOutputStream;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -35,6 +36,7 @@
import com.android.server.policy.WindowManagerPolicy;
import java.io.PrintWriter;
+import java.util.Collections;
class TestWindowManagerPolicy implements WindowManagerPolicy {
@@ -362,4 +364,9 @@
public boolean isGlobalKey(int keyCode) {
return false;
}
+
+ @Override
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ return new KeyboardShortcutGroup("", Collections.emptyList());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9b48cb9..c65b76e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -31,7 +31,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
@@ -551,10 +550,9 @@
}
private static void makeWallpaperWindowShown(WindowState w) {
- final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
- w.mWinAnimator.mSurfaceController = windowSurfaceController;
w.mWinAnimator.mLastAlpha = 1;
- when(windowSurfaceController.getShown()).thenReturn(true);
+ spyOn(w.mWinAnimator);
+ doReturn(true).when(w.mWinAnimator).getShown();
}
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
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 4958b90..89abe2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -253,22 +253,18 @@
final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
spyOn(session);
assertTrue(session.mCanAddInternalSystemWindow);
- final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
- session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
- LayoutParams.TYPE_PHONE);
+ final WindowState window = createWindow(null, LayoutParams.TYPE_PHONE, "win");
+ session.onWindowSurfaceVisibilityChanged(window, true /* visible */);
verify(session).setHasOverlayUi(true);
- session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
- LayoutParams.TYPE_PHONE);
+ session.onWindowSurfaceVisibilityChanged(window, false /* visible */);
verify(session).setHasOverlayUi(false);
}
@Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
- final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
- win.mWinAnimator.mSurfaceController = surfaceController;
win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
- doReturn(true).when(surfaceController).hasSurface();
+ win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
spyOn(win.mTransitionController);
doReturn(true).when(win.mTransitionController).isShellTransitionsEnabled();
doReturn(true).when(win.mTransitionController).inTransition(
@@ -306,7 +302,7 @@
// and WMS#tryStartExitingAnimation() will destroy the surface directly.
assertFalse(win.mAnimatingExit);
assertFalse(win.mHasSurface);
- assertNull(win.mWinAnimator.mSurfaceController);
+ assertNull(win.mWinAnimator.mSurfaceControl);
// Invisible requested activity should not get the last config even if its view is visible.
mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.VISIBLE, 0, 0, 0,
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));
}
}
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
index 2c9361d..f9e004b 100644
--- a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -17,6 +17,7 @@
import android.app.trust.TrustManager
import android.content.Context
+import android.security.Flags.shouldTrustManagerListenForPrimaryAuth
import android.trust.BaseTrustAgentService
import android.trust.TrustTestActivity
import android.trust.test.lib.LockStateTrackingRule
@@ -154,13 +155,17 @@
private fun triggerSuccessfulUnlock() {
screenLockRule.successfulScreenLockAttempt()
- trustAgentRule.reportSuccessfulUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportSuccessfulUnlock()
+ }
await()
}
private fun triggerFailedUnlock() {
screenLockRule.failedScreenLockAttempt()
- trustAgentRule.reportFailedUnlock()
+ if (!shouldTrustManagerListenForPrimaryAuth()) {
+ trustAgentRule.reportFailedUnlock()
+ }
await()
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index cbf2c2f..382b088 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -198,6 +198,7 @@
android::DiagMessage(el->line_number)
<< "Cannot find symbol for android:configChanges with min sdk: "
<< context->GetMinSdkVersion());
+ return false;
}
std::stringstream new_value;