Merge "Remove unused methods" into main
diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp
index 2232345..d645d06 100644
--- a/cmds/gpu_counter_producer/Android.bp
+++ b/cmds/gpu_counter_producer/Android.bp
@@ -19,6 +19,4 @@
"-Wunused",
"-Wunreachable-code",
],
-
- soc_specific: true,
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
new file mode 100644
index 0000000..b89e2cd
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.uinput;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * Parser for the <a href="https://gitlab.freedesktop.org/libevdev/evemu">FreeDesktop evemu</a>
+ * event recording format.
+ */
+public class EvemuParser implements EventParser {
+ private static final String TAG = "UinputEvemuParser";
+
+ /**
+ * The device ID to use for all events. Since evemu files only support single-device
+ * recordings, this will always be the same.
+ */
+ private static final int DEVICE_ID = 1;
+ private static final int REGISTRATION_DELAY_MILLIS = 500;
+
+ private static class CommentAwareReader {
+ private final BufferedReader mReader;
+ private String mNextLine;
+
+ CommentAwareReader(BufferedReader in) throws IOException {
+ mReader = in;
+ mNextLine = findNextLine();
+ }
+
+ private @Nullable String findNextLine() throws IOException {
+ String line = "";
+ while (line != null && line.length() == 0) {
+ String unstrippedLine = mReader.readLine();
+ if (unstrippedLine == null) {
+ // End of file.
+ return null;
+ }
+ line = stripComments(unstrippedLine);
+ }
+ return line;
+ }
+
+ private static String stripComments(String line) {
+ int index = line.indexOf('#');
+ // 'N:' lines (which contain the name of the input device) do not support trailing
+ // comments, to support recording device names that contain #s.
+ if (index < 0 || line.startsWith("N: ")) {
+ return line;
+ } else {
+ return line.substring(0, index).strip();
+ }
+ }
+
+ /**
+ * Returns the next line of the file that isn't blank when stripped of comments, or
+ * {@code null} if the end of the file is reached. However, it does not advance to the
+ * next line of the file.
+ */
+ public @Nullable String peekLine() {
+ return mNextLine;
+ }
+
+ /** Moves to the next line of the file. */
+ public void advance() throws IOException {
+ mNextLine = findNextLine();
+ }
+
+ public boolean isAtEndOfFile() {
+ return mNextLine == null;
+ }
+ }
+
+ private final CommentAwareReader mReader;
+ /**
+ * The timestamp of the last event returned, of the head of {@link #mQueuedEvents} if there is
+ * one, or -1 if no events have been returned yet.
+ */
+ private long mLastEventTimeMicros = -1;
+ private final Queue<Event> mQueuedEvents = new ArrayDeque<>(2);
+
+ public EvemuParser(Reader in) throws IOException {
+ mReader = new CommentAwareReader(new BufferedReader(in));
+ mQueuedEvents.add(parseRegistrationEvent());
+
+ // The kernel takes a little time to set up an evdev device after the initial
+ // registration. Any events that we try to inject during this period would be silently
+ // dropped, so we delay for a short period after registration and before injecting any
+ // events.
+ final Event.Builder delayEb = new Event.Builder();
+ delayEb.setId(DEVICE_ID);
+ delayEb.setCommand(Event.Command.DELAY);
+ delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
+ mQueuedEvents.add(delayEb.build());
+ }
+
+ /**
+ * Returns the next event in the evemu recording.
+ */
+ public Event getNextEvent() throws IOException {
+ if (!mQueuedEvents.isEmpty()) {
+ return mQueuedEvents.remove();
+ }
+
+ if (mReader.isAtEndOfFile()) {
+ return null;
+ }
+
+ final String[] parts = expectLineWithParts("E", 4);
+ final String[] timeParts = parts[0].split("\\.");
+ if (timeParts.length != 2) {
+ throw new RuntimeException("Invalid timestamp (does not contain a '.')");
+ }
+ // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
+ final long timeMicros =
+ Long.parseLong(timeParts[0]) * 1_000_000 + Integer.parseInt(timeParts[1]);
+ final Event.Builder eb = new Event.Builder();
+ eb.setId(DEVICE_ID);
+ eb.setCommand(Event.Command.INJECT);
+ final int eventType = Integer.parseInt(parts[1], 16);
+ final int eventCode = Integer.parseInt(parts[2], 16);
+ final int value = Integer.parseInt(parts[3]);
+ eb.setInjections(new int[] {eventType, eventCode, value});
+
+ if (mLastEventTimeMicros == -1) {
+ // This is the first event being injected, so send it straight away.
+ mLastEventTimeMicros = timeMicros;
+ return eb.build();
+ } else {
+ final long delayMicros = timeMicros - mLastEventTimeMicros;
+ // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
+ // Device class) is 1ms, so ignore time differences smaller than that.
+ if (delayMicros < 1000) {
+ mLastEventTimeMicros = timeMicros;
+ return eb.build();
+ } else {
+ // Send a delay now, and queue the actual event for the next call.
+ mQueuedEvents.add(eb.build());
+ mLastEventTimeMicros = timeMicros;
+ final Event.Builder delayEb = new Event.Builder();
+ delayEb.setId(DEVICE_ID);
+ delayEb.setCommand(Event.Command.DELAY);
+ delayEb.setDurationMillis((int) (delayMicros / 1000));
+ return delayEb.build();
+ }
+ }
+ }
+
+ private Event parseRegistrationEvent() throws IOException {
+ // The registration details at the start of a recording are specified by a set of lines
+ // that have to be in this order: N, I, P, B, A, L, S. Recordings must have exactly one N
+ // (name) and I (IDs) line. The remaining lines are optional, and there may be multiple
+ // of those lines.
+
+ final Event.Builder eb = new Event.Builder();
+ eb.setId(DEVICE_ID);
+ eb.setCommand(Event.Command.REGISTER);
+ eb.setName(expectLine("N"));
+
+ final String[] idStrings = expectLineWithParts("I", 4);
+ eb.setBusId(Integer.parseInt(idStrings[0], 16));
+ eb.setVid(Integer.parseInt(idStrings[1], 16));
+ eb.setPid(Integer.parseInt(idStrings[2], 16));
+ // TODO(b/302297266): support setting the version ID, and set it to idStrings[3].
+
+ final SparseArray<int[]> config = new SparseArray<>();
+ config.append(Event.UinputControlCode.UI_SET_PROPBIT.getValue(), parseProperties());
+
+ parseAxisBitmaps(config);
+
+ eb.setConfiguration(config);
+ if (config.contains(Event.UinputControlCode.UI_SET_FFBIT.getValue())) {
+ // If the device specifies any force feedback effects, the kernel will require the
+ // ff_effects_max value to be set.
+ eb.setFfEffectsMax(config.get(Event.UinputControlCode.UI_SET_FFBIT.getValue()).length);
+ }
+
+ eb.setAbsInfo(parseAbsInfos());
+
+ // L: and S: lines allow the initial states of the device's LEDs and switches to be
+ // recorded. However, the FreeDesktop implementation doesn't support actually setting these
+ // states at the start of playback (apparently due to concerns over race conditions), and we
+ // have no need for this feature either, so for now just skip over them.
+ skipUnsupportedLines("L");
+ skipUnsupportedLines("S");
+
+ return eb.build();
+ }
+
+ private int[] parseProperties() throws IOException {
+ final List<String> propBitmapParts = new ArrayList<>();
+ String line = acceptLine("P");
+ while (line != null) {
+ propBitmapParts.addAll(List.of(line.strip().split(" ")));
+ line = acceptLine("P");
+ }
+ return hexStringBitmapToEventCodes(propBitmapParts);
+ }
+
+ private void parseAxisBitmaps(SparseArray<int[]> config) throws IOException {
+ final Map<Integer, List<String>> axisBitmapParts = new HashMap<>();
+ String line = acceptLine("B");
+ while (line != null) {
+ final String[] parts = line.strip().split(" ");
+ if (parts.length < 2) {
+ throw new RuntimeException(
+ "Expected event type and at least one bitmap byte on 'B:' line; only found "
+ + parts.length + " elements");
+ }
+ final int eventType = Integer.parseInt(parts[0], 16);
+ // EV_SYN cannot be configured through uinput, so skip it.
+ if (eventType != Event.EV_SYN) {
+ if (!axisBitmapParts.containsKey(eventType)) {
+ axisBitmapParts.put(eventType, new ArrayList<>());
+ }
+ for (int i = 1; i < parts.length; i++) {
+ axisBitmapParts.get(eventType).add(parts[i]);
+ }
+ }
+ line = acceptLine("B");
+ }
+ final List<Integer> eventTypesToSet = new ArrayList<>();
+ for (var entry : axisBitmapParts.entrySet()) {
+ if (entry.getValue().size() == 0) {
+ continue;
+ }
+ final Event.UinputControlCode controlCode =
+ Event.UinputControlCode.forEventType(entry.getKey());
+ final int[] eventCodes = hexStringBitmapToEventCodes(entry.getValue());
+ if (controlCode != null && eventCodes.length > 0) {
+ config.append(controlCode.getValue(), eventCodes);
+ eventTypesToSet.add(entry.getKey());
+ }
+ }
+ config.append(
+ Event.UinputControlCode.UI_SET_EVBIT.getValue(), unboxIntList(eventTypesToSet));
+ }
+
+ private SparseArray<InputAbsInfo> parseAbsInfos() throws IOException {
+ final SparseArray<InputAbsInfo> absInfos = new SparseArray<>();
+ String line = acceptLine("A");
+ while (line != null) {
+ final String[] parts = line.strip().split(" ");
+ if (parts.length < 5 || parts.length > 6) {
+ throw new RuntimeException(
+ "'A:' lines should have the format 'A: <index (hex)> <min> <max> <fuzz> "
+ + "<flat> [<resolution>]'; expected 5 or 6 numbers but found "
+ + parts.length);
+ }
+ final int axisCode = Integer.parseInt(parts[0], 16);
+ final InputAbsInfo info = new InputAbsInfo();
+ info.minimum = Integer.parseInt(parts[1]);
+ info.maximum = Integer.parseInt(parts[2]);
+ info.fuzz = Integer.parseInt(parts[3]);
+ info.flat = Integer.parseInt(parts[4]);
+ info.resolution = parts.length > 5 ? Integer.parseInt(parts[5]) : 0;
+ absInfos.append(axisCode, info);
+ line = acceptLine("A");
+ }
+ return absInfos;
+ }
+
+ private void skipUnsupportedLines(String type) throws IOException {
+ if (acceptLine(type) != null) {
+ while (acceptLine(type) != null) {
+ // Skip the line.
+ }
+ }
+ }
+
+ /**
+ * Returns the contents of the next line in the file if it has the given type, or raises an
+ * error if it does not.
+ *
+ * @param type the type of the line to expect, represented by the letter before the ':'.
+ * @return the part of the line after the ": ".
+ */
+ private String expectLine(String type) throws IOException {
+ final String line = acceptLine(type);
+ if (line == null) {
+ throw new RuntimeException("Expected line of type '" + type + "'");
+ } else {
+ return line;
+ }
+ }
+
+ /**
+ * Peeks at the next line in the file to see if it has the given type, and if so, returns its
+ * contents and advances the reader.
+ *
+ * @param type the type of the line to accept, represented by the letter before the ':'.
+ * @return the part of the line after the ": ", if the type matches; otherwise {@code null}.
+ */
+ private @Nullable String acceptLine(String type) throws IOException {
+ final String line = mReader.peekLine();
+ if (line == null) {
+ return null;
+ }
+ final String[] lineParts = line.split(": ", 2);
+ if (lineParts.length < 2) {
+ // TODO(b/302297266): make a proper exception class for syntax errors, including line
+ // numbers, etc.. (We can use LineNumberReader to track them.)
+ throw new RuntimeException("Line without ': '");
+ }
+ if (lineParts[0].equals(type)) {
+ mReader.advance();
+ return lineParts[1];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Like {@link #expectLine(String)}, but also checks that the contents of the line is formed of
+ * {@code numParts} space-separated parts.
+ *
+ * @param type the type of the line to expect, represented by the letter before the ':'.
+ * @param numParts the number of parts to expect.
+ * @return the part of the line after the ": ", split into {@code numParts} sections.
+ */
+ private String[] expectLineWithParts(String type, int numParts) throws IOException {
+ final String[] parts = expectLine(type).strip().split(" ");
+ if (parts.length != numParts) {
+ throw new RuntimeException("Expected a '" + type + "' line with " + numParts
+ + " parts, found one with " + parts.length);
+ }
+ return parts;
+ }
+
+ private static int[] hexStringBitmapToEventCodes(List<String> strs) {
+ final List<Integer> codes = new ArrayList<>();
+ for (int iByte = 0; iByte < strs.size(); iByte++) {
+ int b = Integer.parseInt(strs.get(iByte), 16);
+ if (b < 0x0 || b > 0xff) {
+ throw new RuntimeException("Bitmap part '" + strs.get(iByte)
+ + "' invalid; parts must be between 00 and ff.");
+ }
+ for (int iBit = 0; iBit < 8; iBit++) {
+ if ((b & 1) != 0) {
+ codes.add(iByte * 8 + iBit);
+ }
+ b >>= 1;
+ }
+ }
+ return unboxIntList(codes);
+ }
+
+ private static int[] unboxIntList(List<Integer> list) {
+ final int[] array = new int[list.size()];
+ Arrays.setAll(array, list::get);
+ return array;
+ }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 4498bc2..5ec40e5 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -16,6 +16,7 @@
package com.android.commands.uinput;
+import android.annotation.Nullable;
import android.util.SparseArray;
import java.util.Arrays;
@@ -39,6 +40,7 @@
// Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
// kernel.
+ public static final int EV_SYN = 0x00;
public static final int EV_KEY = 0x01;
public static final int EV_REL = 0x02;
public static final int EV_ABS = 0x03;
@@ -69,19 +71,23 @@
public int getValue() {
return mValue;
}
- }
- // These constants come from "include/uapi/linux/input.h" in the kernel
- enum Bus {
- USB(0x03), BLUETOOTH(0x05);
- private final int mValue;
-
- Bus(int value) {
- mValue = value;
- }
-
- int getValue() {
- return mValue;
+ /**
+ * Returns the control code for the given evdev event type, or {@code null} if there is no
+ * control code for that type.
+ */
+ public static @Nullable UinputControlCode forEventType(int eventType) {
+ return switch (eventType) {
+ case EV_KEY -> UI_SET_KEYBIT;
+ case EV_REL -> UI_SET_RELBIT;
+ case EV_ABS -> UI_SET_ABSBIT;
+ case EV_MSC -> UI_SET_MSCBIT;
+ case EV_SW -> UI_SET_SWBIT;
+ case EV_LED -> UI_SET_LEDBIT;
+ case EV_SND -> UI_SET_SNDBIT;
+ case EV_FF -> UI_SET_FFBIT;
+ default -> null;
+ };
}
}
@@ -90,7 +96,7 @@
private String mName;
private int mVid;
private int mPid;
- private Bus mBus;
+ private int mBusId;
private int[] mInjections;
private SparseArray<int[]> mConfiguration;
private int mDurationMillis;
@@ -120,7 +126,7 @@
}
public int getBus() {
- return mBus.getValue();
+ return mBusId;
}
public int[] getInjections() {
@@ -168,7 +174,7 @@
+ ", name=" + mName
+ ", vid=" + mVid
+ ", pid=" + mPid
- + ", bus=" + mBus
+ + ", busId=" + mBusId
+ ", events=" + Arrays.toString(mInjections)
+ ", configuration=" + mConfiguration
+ ", duration=" + mDurationMillis + "ms"
@@ -218,8 +224,8 @@
mEvent.mPid = pid;
}
- public void setBus(Bus bus) {
- mEvent.mBus = bus;
+ public void setBusId(int busId) {
+ mEvent.mBusId = busId;
}
public void setDurationMillis(int durationMillis) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/EventParser.java b/cmds/uinput/src/com/android/commands/uinput/EventParser.java
new file mode 100644
index 0000000..a4df03d
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/EventParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.commands.uinput;
+
+import java.io.IOException;
+
+/**
+ * Interface for a class that reads a stream of {@link Event}s.
+ */
+public interface EventParser {
+ /**
+ * Returns the next event in the file that the parser is reading from.
+ */
+ Event getNextEvent() throws IOException;
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
index a2195c7..888ec5a 100644
--- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -22,7 +22,7 @@
import android.util.SparseArray;
import java.io.IOException;
-import java.io.InputStreamReader;
+import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -34,12 +34,12 @@
/**
* A class that parses the JSON-like event format described in the README to build {@link Event}s.
*/
-public class JsonStyleParser {
+public class JsonStyleParser implements EventParser {
private static final String TAG = "UinputJsonStyleParser";
private JsonReader mReader;
- public JsonStyleParser(InputStreamReader in) {
+ public JsonStyleParser(Reader in) {
mReader = new JsonReader(in);
mReader.setLenient(true);
}
@@ -62,7 +62,7 @@
case "name" -> eb.setName(mReader.nextString());
case "vid" -> eb.setVid(readInt());
case "pid" -> eb.setPid(readInt());
- case "bus" -> eb.setBus(readBus());
+ case "bus" -> eb.setBusId(readBus());
case "events" -> {
int[] injections = readInjectedEvents().stream()
.mapToInt(Integer::intValue).toArray();
@@ -139,9 +139,35 @@
});
}
- private Event.Bus readBus() throws IOException {
+ private int readBus() throws IOException {
String val = mReader.nextString();
- return Event.Bus.valueOf(val.toUpperCase());
+ // See include/uapi/linux/input.h in the kernel for the source of these constants.
+ return switch (val.toUpperCase()) {
+ case "PCI" -> 0x01;
+ case "ISAPNP" -> 0x02;
+ case "USB" -> 0x03;
+ case "HIL" -> 0x04;
+ case "BLUETOOTH" -> 0x05;
+ case "VIRTUAL" -> 0x06;
+ case "ISA" -> 0x10;
+ case "I8042" -> 0x11;
+ case "XTKBD" -> 0x12;
+ case "RS232" -> 0x13;
+ case "GAMEPORT" -> 0x14;
+ case "PARPORT" -> 0x15;
+ case "AMIGA" -> 0x16;
+ case "ADB" -> 0x17;
+ case "I2C" -> 0x18;
+ case "HOST" -> 0x19;
+ case "GSC" -> 0x1A;
+ case "ATARI" -> 0x1B;
+ case "SPI" -> 0x1C;
+ case "RMI" -> 0x1D;
+ case "CEC" -> 0x1E;
+ case "INTEL_ISHTP" -> 0x1F;
+ case "AMD_SFH" -> 0x20;
+ default -> throw new IllegalArgumentException("Invalid bus ID " + val);
+ };
}
private SparseArray<int[]> readConfiguration()
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index fe76abb..684a12f 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -19,12 +19,12 @@
import android.util.Log;
import android.util.SparseArray;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.UnsupportedEncodingException;
import java.util.Objects;
/**
@@ -35,7 +35,7 @@
public class Uinput {
private static final String TAG = "UINPUT";
- private final JsonStyleParser mParser;
+ private final EventParser mParser;
private final SparseArray<Device> mDevices;
private static void usage() {
@@ -74,12 +74,32 @@
private Uinput(InputStream in) {
mDevices = new SparseArray<Device>();
try {
- mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8"));
- } catch (UnsupportedEncodingException e) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ mParser = isEvemuFile(reader) ? new EvemuParser(reader) : new JsonStyleParser(reader);
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
+ private boolean isEvemuFile(BufferedReader in) throws IOException {
+ // After zero or more empty lines (not even containing horizontal whitespace), evemu
+ // recordings must either start with '#' (indicating the EVEMU version header or a comment)
+ // or 'N' (for the name line). If we encounter anything else, assume it's a JSON-style input
+ // file.
+
+ String lineSep = System.lineSeparator();
+ char[] buf = new char[1];
+
+ in.mark(1 /* readAheadLimit */);
+ int charsRead = in.read(buf);
+ while (charsRead > 0 && lineSep.contains(String.valueOf(buf[0]))) {
+ in.mark(1 /* readAheadLimit */);
+ charsRead = in.read(buf);
+ }
+ in.reset();
+ return buf[0] == '#' || buf[0] == 'N';
+ }
+
private void run() {
try {
Event e = null;
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 275fe77..c282e4b6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -307,7 +307,7 @@
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
- field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
+ field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b5f7f23..6c10f49 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7604,15 +7604,17 @@
* @param taskDescription The TaskDescription properties that describe the task with this activity
*/
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
- if (mTaskDescription != taskDescription) {
- mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
- // Scale the icon down to something reasonable if it is provided
- if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
- final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
- final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
- true);
- mTaskDescription.setIcon(Icon.createWithBitmap(icon));
- }
+ if (taskDescription == null || mTaskDescription.equals(taskDescription)) {
+ return;
+ }
+
+ mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
+ // Scale the icon down to something reasonable if it is provided
+ if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
+ final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+ final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
+ true);
+ mTaskDescription.setIcon(Icon.createWithBitmap(icon));
}
ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7b075e6..4d208c6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2301,6 +2301,7 @@
<!-- Allows system apps to call methods to register itself as a mDNS offload engine.
<p>Not for use by third-party or privileged applications.
@SystemApi
+ @FlaggedApi("com.android.net.flags.register_nsd_offload_engine")
@hide This should only be used by system apps.
-->
<permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
index b528089d15..5c02dbc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.transition;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
@@ -61,9 +62,10 @@
}
final int mode = change.getMode();
+ final boolean isBackGesture = change.hasFlags(FLAG_BACK_GESTURE_ANIMATED);
if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
- && TransitionUtil.isOpenOrCloseMode(mode)) {
- notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode));
+ && (TransitionUtil.isOpenOrCloseMode(mode) || isBackGesture)) {
+ notifyHomeVisibilityChanged(TransitionUtil.isOpeningType(mode) || isBackGesture);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index a5629c8..b355ab0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -14,8 +14,6 @@
import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import androidx.test.filters.SmallTest
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -28,8 +26,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index ea7c0d9..421c445 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -18,8 +18,10 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -145,6 +147,26 @@
verify(mListener, times(0)).onHomeVisibilityChanged(anyBoolean());
}
+ @Test
+ public void testHomeActivityWithBackGestureNotifiesHomeIsVisible() throws RemoteException {
+ TransitionInfo info = mock(TransitionInfo.class);
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ ActivityManager.RunningTaskInfo taskInfo = mock(ActivityManager.RunningTaskInfo.class);
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(info.getChanges()).thenReturn(new ArrayList<>(List.of(change)));
+
+ when(change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)).thenReturn(true);
+ setupTransitionInfo(taskInfo, change, ACTIVITY_TYPE_HOME, TRANSIT_CHANGE);
+
+ mHomeTransitionObserver.onTransitionReady(mock(IBinder.class),
+ info,
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class));
+
+ verify(mListener, times(1)).onHomeVisibilityChanged(true);
+ }
+
+
/**
* Helper class to initialize variables for the rest.
*/
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 93a5082..071f9f4 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -116,6 +116,9 @@
base: 'w'
shift, capslock: 'W'
shift+capslock: 'w'
+ ralt: '\u1e83'
+ shift+ralt, capslock+ralt: '\u1e82'
+ shift+capslock+ralt: '\u1e83'
}
key E {
@@ -147,6 +150,9 @@
base: 'y'
shift, capslock: 'Y'
shift+capslock: 'y'
+ ralt: '\u00fd'
+ shift+ralt, capslock+ralt: '\u00dd'
+ shift+capslock+ralt: '\u00fd'
}
key U {
@@ -313,6 +319,9 @@
base: 'c'
shift, capslock: 'C'
shift+capslock: 'c'
+ ralt: '\u00e7'
+ shift+ralt, capslock+ralt: '\u00c7'
+ shift+capslock+ralt: '\u00e7'
}
key V {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 4906304..636f98d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -44,7 +44,7 @@
label: '2'
base: '\u00e9'
shift: '2'
- ralt: '~'
+ ralt: '\u0303'
}
key 3 {
@@ -79,7 +79,7 @@
label: '7'
base: '\u00e8'
shift: '7'
- ralt: '`'
+ ralt: '\u0300'
}
key 8 {
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 7f23f74..ee49b23 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -94,7 +94,7 @@
android:name="keyboard_layout_swiss_german"
android:label="@string/keyboard_layout_swiss_german_label"
android:keyboardLayout="@raw/keyboard_layout_swiss_german"
- android:keyboardLocale="de-Latn-CH"
+ android:keyboardLocale="de-Latn-CH|gsw-Latn-CH"
android:keyboardLayoutType="qwertz" />
<keyboard-layout
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 83c16ae..6a0e882 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -19,6 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import javax.inject.Inject
/** A class in which engineers can define flag dependencies */
@@ -26,6 +27,7 @@
class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, handler: Handler) :
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
+ NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
index 747efe3..933d0ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
@@ -36,7 +36,8 @@
/**
* A mode where notification icons in the status bar are hidden and replaced by a dot (this mode
* can be requested by apps). See
- * [com.android.systemui.statusbar.phone.LightsOutNotifController].
+ * [com.android.systemui.statusbar.phone.LegacyLightsOutNotifController] and
+ * [com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor].
*/
LIGHTS_OUT,
/** Similar to [LIGHTS_OUT], but also with a transparent background for the status bar. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index d1594ef..0415212 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -33,7 +33,7 @@
/**
* Repository for data that's specific to the status bar **on keyguard**. For data that applies to
- * all status bars, use [StatusBarModeRepository].
+ * all status bars, use [StatusBarModeRepositoryStore].
*/
interface KeyguardStatusBarRepository {
/** True if we can show the user switcher on keyguard and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 47994d9..6429815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -25,10 +25,8 @@
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
-import com.android.systemui.CoreStartable
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.data.model.StatusBarAppearance
@@ -38,13 +36,10 @@
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
-import dagger.Binds
-import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import java.io.PrintWriter
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -61,7 +56,7 @@
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModeRepository {
+interface StatusBarModePerDisplayRepository {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -108,16 +103,15 @@
fun clearTransient()
}
-@SysUISingleton
-class StatusBarModeRepositoryImpl
-@Inject
+class StatusBarModePerDisplayRepositoryImpl
+@AssistedInject
constructor(
@Application scope: CoroutineScope,
- @DisplayId thisDisplayId: Int,
+ @Assisted("displayId") thisDisplayId: Int,
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
+) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -166,7 +160,7 @@
}
}
- override fun start() {
+ fun start() {
commandQueue.addCallback(commandQueueCallback)
}
@@ -340,16 +334,7 @@
)
}
-@Module
-interface StatusBarModeRepositoryModule {
- @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryImpl::class)
- fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
- @Binds
- @IntoSet
- fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+@AssistedFactory
+interface StatusBarModePerDisplayRepositoryFactory {
+ fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
new file mode 100644
index 0000000..962cb095
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
+import java.io.PrintWriter
+import javax.inject.Inject
+
+interface StatusBarModeRepositoryStore {
+ val defaultDisplay: StatusBarModePerDisplayRepository
+ fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+}
+
+@SysUISingleton
+class StatusBarModeRepositoryImpl
+@Inject
+constructor(
+ @DisplayId private val displayId: Int,
+ factory: StatusBarModePerDisplayRepositoryFactory
+) :
+ StatusBarModeRepositoryStore,
+ CoreStartable,
+ StatusBarInitializer.OnStatusBarViewInitializedListener {
+ override val defaultDisplay = factory.create(displayId)
+
+ override fun forDisplay(displayId: Int) =
+ if (this.displayId == displayId) {
+ defaultDisplay
+ } else {
+ TODO("b/127878649 implement multi-display state management")
+ }
+
+ override fun start() {
+ defaultDisplay.start()
+ }
+
+ override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+ defaultDisplay.onStatusBarViewInitialized(component)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ defaultDisplay.dump(pw, args)
+ }
+}
+
+@Module
+interface StatusBarModeRepositoryModule {
+ @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryStore::class)
+ fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+ @Binds
+ @IntoSet
+ fun bindViewInitListener(
+ impl: StatusBarModeRepositoryImpl
+ ): StatusBarInitializer.OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
index 44387c2..8fc7106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationsLiveDataStoreRefactor.kt
@@ -50,4 +50,4 @@
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b3fe79e..57d49b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -199,7 +199,7 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -387,7 +387,7 @@
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarInitializer mStatusBarInitializer;
private final StatusBarWindowController mStatusBarWindowController;
- private final StatusBarModeRepository mStatusBarModeRepository;
+ private final StatusBarModeRepositoryStore mStatusBarModeRepository;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
DozeServiceHost mDozeServiceHost;
@@ -605,7 +605,7 @@
StatusBarInitializer statusBarInitializer,
StatusBarWindowController statusBarWindowController,
StatusBarWindowStateController statusBarWindowStateController,
- StatusBarModeRepository statusBarModeRepository,
+ StatusBarModeRepositoryStore statusBarModeRepository,
KeyguardUpdateMonitor keyguardUpdateMonitor,
StatusBarSignalPolicy statusBarSignalPolicy,
PulseExpansionHandler pulseExpansionHandler,
@@ -899,7 +899,7 @@
setUpPresenter();
if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
- mStatusBarModeRepository.showTransient();
+ mStatusBarModeRepository.getDefaultDisplay().showTransient();
}
mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
@@ -1146,9 +1146,10 @@
mDemoModeController.addCallback(mDemoModeCallback);
mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.isTransientShown(), this::onTransientShownChanged);
+ mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
+ this::onTransientShownChanged);
mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getStatusBarMode(),
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
this::updateBarMode);
mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
@@ -1208,7 +1209,7 @@
@Override
public void hide() {
- mStatusBarModeRepository.clearTransient();
+ mStatusBarModeRepository.getDefaultDisplay().clearTransient();
}
});
@@ -1656,7 +1657,7 @@
if (mDemoModeController.isInDemoMode()) return;
if (mStatusBarTransitions != null) {
checkBarMode(
- mStatusBarModeRepository.getStatusBarMode().getValue(),
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(),
mStatusBarWindowState,
mStatusBarTransitions);
}
@@ -1667,7 +1668,8 @@
/** Temporarily hides Bubbles if the status bar is hidden. */
@Override
public void updateBubblesVisibility() {
- StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue();
+ StatusBarMode mode =
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue();
mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
mode != StatusBarMode.LIGHTS_OUT
&& mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT
@@ -2953,7 +2955,7 @@
// End Extra BaseStatusBarMethods.
boolean isTransientShown() {
- return mStatusBarModeRepository.isTransientShown().getValue();
+ return mStatusBarModeRepository.getDefaultDisplay().isTransientShown().getValue();
}
private void updateLightRevealScrimVisibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
index eba7fe0..7c871e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifController.java
@@ -36,6 +36,7 @@
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
import com.android.systemui.util.ViewController;
@@ -51,7 +52,7 @@
* whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
*/
@StatusBarFragmentScope
-public class LightsOutNotifController extends ViewController<View> {
+public class LegacyLightsOutNotifController extends ViewController<View> {
private final CommandQueue mCommandQueue;
private final NotifLiveDataStore mNotifDataStore;
private final WindowManager mWindowManager;
@@ -63,7 +64,7 @@
private int mDisplayId;
@Inject
- LightsOutNotifController(
+ LegacyLightsOutNotifController(
@Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView,
WindowManager windowManager,
NotifLiveDataStore notifDataStore,
@@ -72,7 +73,12 @@
mWindowManager = windowManager;
mNotifDataStore = notifDataStore;
mCommandQueue = commandQueue;
+ }
+ @Override
+ protected void onInit() {
+ super.onInit();
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 4d3e2ad..eec617b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -42,7 +42,7 @@
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -68,7 +68,7 @@
private final JavaAdapter mJavaAdapter;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final StatusBarModeRepository mStatusBarModeRepository;
+ private final StatusBarModeRepositoryStore mStatusBarModeRepository;
private BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
@@ -126,7 +126,7 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- StatusBarModeRepository statusBarModeRepository,
+ StatusBarModeRepositoryStore statusBarModeRepository,
DumpManager dumpManager,
DisplayTracker displayTracker) {
mJavaAdapter = javaAdapter;
@@ -146,7 +146,7 @@
@Override
public void start() {
mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getStatusBarAppearance(),
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
this::onStatusBarAppearanceChanged);
}
@@ -476,7 +476,7 @@
private final DarkIconDispatcher mDarkIconDispatcher;
private final BatteryController mBatteryController;
private final NavigationModeController mNavModeController;
- private final StatusBarModeRepository mStatusBarModeRepository;
+ private final StatusBarModeRepositoryStore mStatusBarModeRepository;
private final DumpManager mDumpManager;
private final DisplayTracker mDisplayTracker;
@@ -486,7 +486,7 @@
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- StatusBarModeRepository statusBarModeRepository,
+ StatusBarModeRepositoryStore statusBarModeRepository,
DumpManager dumpManager,
DisplayTracker displayTracker) {
mJavaAdapter = javaAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt
new file mode 100644
index 0000000..ed8b3e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractor.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where status
+ * bar and navigation icons dim. In this mode, a notification dot appears where the notification
+ * icons would appear if they would be shown outside of this mode.
+ *
+ * This interactor knows whether the device is in [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
+ */
+@SysUISingleton
+class LightsOutInteractor
+@Inject
+constructor(private val repository: StatusBarModeRepositoryStore) {
+
+ fun isLowProfile(displayId: Int): Flow<Boolean> =
+ repository.forDisplay(displayId).statusBarMode.map {
+ when (it) {
+ StatusBarMode.LIGHTS_OUT,
+ StatusBarMode.LIGHTS_OUT_TRANSPARENT -> true
+ else -> false
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 7adc08c..49880d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -43,7 +43,6 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -139,7 +138,6 @@
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
- private final FeatureFlagsClassic mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
@@ -228,7 +226,6 @@
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
ShadeExpansionStateManager shadeExpansionStateManager,
- FeatureFlagsClassic featureFlags,
StatusBarIconController statusBarIconController,
DarkIconManager.Factory darkIconManagerFactory,
CollapsedStatusBarViewModel collapsedStatusBarViewModel,
@@ -258,7 +255,6 @@
mLocationPublisher = locationPublisher;
mNotificationIconAreaController = notificationIconAreaController;
mShadeExpansionStateManager = shadeExpansionStateManager;
- mFeatureFlags = featureFlags;
mStatusBarIconController = statusBarIconController;
mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 0618abb..96faa35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -18,8 +18,9 @@
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.LegacyLightsOutNotifController;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
@@ -78,7 +79,9 @@
getBatteryMeterViewController().init();
getHeadsUpAppearanceController().init();
getPhoneStatusBarViewController().init();
- getLightsOutNotifController().init();
+ if (!NotificationsLiveDataStoreRefactor.isEnabled()) {
+ getLegacyLightsOutNotifController().init();
+ }
getStatusBarDemoMode().init();
}
@@ -101,7 +104,7 @@
/** */
@StatusBarFragmentScope
- LightsOutNotifController getLightsOutNotifController();
+ LegacyLightsOutNotifController getLegacyLightsOutNotifController();
/** */
@StatusBarFragmentScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index b0532ce..0bdd1a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -36,7 +36,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -68,7 +68,7 @@
private val dumpManager: DumpManager,
private val statusBarWindowController: StatusBarWindowController,
private val swipeStatusBarAwayGestureHandler: SwipeStatusBarAwayGestureHandler,
- private val statusBarModeRepository: StatusBarModeRepository,
+ private val statusBarModeRepository: StatusBarModeRepositoryStore,
) : CallbackController<OngoingCallListener>, Dumpable, CoreStartable {
private var isFullscreen: Boolean = false
/** Non-null if there's an active call notification. */
@@ -129,7 +129,7 @@
dumpManager.registerDumpable(this)
notifCollection.addCollectionListener(notifListener)
scope.launch {
- statusBarModeRepository.isInFullscreenMode.collect {
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.collect {
isFullscreen = it
updateChipClickListener()
updateGestureListening()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
index da9c45a..9c78ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt
@@ -27,7 +27,7 @@
*
* This class is used to break a dependency cycle between
* [com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController] and
- * [com.android.systemui.statusbar.data.repository.StatusBarModeRepository]. Instead, those two
+ * [com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore]. Instead, those two
* classes both refer to this repository.
*/
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index b9b88f4..7d7f49b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.pipeline.shared.ui.binder
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
import javax.inject.Inject
import kotlinx.coroutines.launch
@@ -61,9 +65,49 @@
listener.onTransitionFromLockscreenToDreamStarted()
}
}
+
+ if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ val displayId = view.display.displayId
+ val lightsOutView: View = view.requireViewById(R.id.notification_lights_out)
+ launch {
+ viewModel.areNotificationsLightsOut(displayId).collect { show ->
+ animateLightsOutView(lightsOutView, show)
+ }
+ }
+ }
}
}
}
+
+ private fun animateLightsOutView(view: View, visible: Boolean) {
+ view.animate().cancel()
+
+ val alpha = if (visible) 1f else 0f
+ val duration = if (visible) 750L else 250L
+ val visibility = if (visible) View.VISIBLE else View.GONE
+
+ if (visible) {
+ view.alpha = 0f
+ view.visibility = View.VISIBLE
+ }
+
+ view
+ .animate()
+ .alpha(alpha)
+ .setDuration(duration)
+ .setListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ view.alpha = alpha
+ view.visibility = visibility
+ // Unset the listener, otherwise this may persist for
+ // another view property animation
+ view.animate().setListener(null)
+ }
+ }
+ )
+ .start()
+ }
}
/** Listener for various events that may affect the status bar's visibility. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index 15ab143..52a6d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -20,11 +20,17 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
+import com.android.systemui.statusbar.phone.domain.interactor.LightsOutInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -48,12 +54,25 @@
/** Emits whenever a transition from lockscreen to dream has started. */
val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
+
+ /**
+ * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
+ * status bar and navigation icons dim. In this mode, a notification dot appears where the
+ * notification icons would appear if they would be shown outside of this mode.
+ *
+ * This flow tells when to show or hide the notification dot in the status bar to indicate
+ * whether there are notifications when the device is in
+ * [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE].
+ */
+ fun areNotificationsLightsOut(displayId: Int): Flow<Boolean>
}
@SysUISingleton
class CollapsedStatusBarViewModelImpl
@Inject
constructor(
+ private val lightsOutInteractor: LightsOutInteractor,
+ private val notificationsInteractor: ActiveNotificationsInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
@Application coroutineScope: CoroutineScope,
) : CollapsedStatusBarViewModel {
@@ -69,4 +88,17 @@
keyguardTransitionInteractor.lockscreenToDreamingTransition
.filter { it.transitionState == TransitionState.STARTED }
.map {}
+
+ override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> =
+ if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) {
+ emptyFlow()
+ } else {
+ combine(
+ notificationsInteractor.areAnyNotificationsPresent,
+ lightsOutInteractor.isLowProfile(displayId),
+ ) { hasNotifications, isLowProfile ->
+ hasNotifications && isLowProfile
+ }
+ .distinctUntilChanged()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d65a69c..9ee3d22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -64,13 +64,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.settingslib.volume.MediaSessions;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.RingerModeLiveData;
@@ -99,6 +99,7 @@
@SysUISingleton
public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
private static final String TAG = Util.logTag(VolumeDialogControllerImpl.class);
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int TOUCH_FEEDBACK_TIMEOUT_MS = 1000;
private static final int DYNAMIC_STREAM_START_INDEX = 100;
@@ -1339,14 +1340,24 @@
private boolean showForSession(Token token) {
if (mVolumeAdjustmentForRemoteGroupSessions) {
+ if (DEBUG) {
+ Log.d(TAG, "Volume adjustment for remote group sessions allowed,"
+ + " showForSession: true");
+ }
return true;
}
MediaController ctr = new MediaController(mContext, token);
String packageName = ctr.getPackageName();
List<RoutingSessionInfo> sessions =
mRouter2Manager.getRoutingSessions(packageName);
-
+ if (DEBUG) {
+ Log.d(TAG, "Found " + sessions.size() + " routing sessions for package name "
+ + packageName);
+ }
for (RoutingSessionInfo session : sessions) {
+ if (DEBUG) {
+ Log.d(TAG, "Found routingSessionInfo: " + session);
+ }
if (!session.isSystemSession()
&& session.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED) {
return true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
index d1a46fc..057dcb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryImplTest.kt
@@ -62,7 +62,7 @@
private val ongoingCallRepository = OngoingCallRepository()
private val underTest =
- StatusBarModeRepositoryImpl(
+ StatusBarModePerDisplayRepositoryImpl(
testScope.backgroundScope,
DISPLAY_ID,
commandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
index 287ebba..bde2243 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java
@@ -54,7 +54,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
-public class LightsOutNotifControllerTest extends SysuiTestCase {
+public class LegacyLightsOutNotifControllerTest extends SysuiTestCase {
private static final int LIGHTS_ON = 0;
private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS;
@@ -68,7 +68,7 @@
@Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor;
private View mLightsOutView;
- private LightsOutNotifController mLightsOutNotifController;
+ private LegacyLightsOutNotifController mLightsOutNotifController;
private int mDisplayId;
private Observer<Boolean> mHaActiveNotifsObserver;
private CommandQueue.Callbacks mCallbacks;
@@ -83,7 +83,7 @@
when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs);
when(mHasActiveNotifs.getValue()).thenReturn(false);
- mLightsOutNotifController = new LightsOutNotifController(
+ mLightsOutNotifController = new LegacyLightsOutNotifController(
mLightsOutView,
mWindowManager,
mNotifLiveDataStore,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index c45ecf3..f6419a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -121,7 +121,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -142,7 +142,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -165,7 +165,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -190,7 +190,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -214,7 +214,7 @@
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -231,7 +231,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -249,7 +249,7 @@
new AppearanceRegion(0, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -266,7 +266,7 @@
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -276,7 +276,7 @@
reset(mStatusBarIconController);
// WHEN the same appearance regions but different status bar mode is sent
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.LIGHTS_OUT_TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -298,7 +298,7 @@
/* start= */ new Rect(0, 0, 10, 10),
/* end= */ new Rect(0, 0, 20, 20));
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
startingBounds,
@@ -311,7 +311,7 @@
BoundsPair newBounds = new BoundsPair(
/* start= */ new Rect(0, 0, 30, 30),
/* end= */ new Rect(0, 0, 40, 40));
- mStatusBarModeRepository.getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
newBounds,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
new file mode 100644
index 0000000..5a0e13d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/domain/interactor/LightsOutInteractorTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class LightsOutInteractorTest : SysuiTestCase() {
+
+ private val statusBarModeRepository = FakeStatusBarModeRepository()
+ private val interactor: LightsOutInteractor = LightsOutInteractor(statusBarModeRepository)
+
+ @Test
+ fun isLowProfile_lightsOutStatusBarMode_false() = runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.LIGHTS_OUT
+
+ val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLowProfile_lightsOutTransparentStatusBarMode_true() = runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value =
+ StatusBarMode.LIGHTS_OUT_TRANSPARENT
+
+ val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLowProfile_transparentStatusBarMode_false() = runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+
+ val actual by collectLastValue(interactor.isLowProfile(DISPLAY_ID))
+
+ assertThat(actual).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 0b87fe8..17c2938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -46,7 +46,6 @@
import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
@@ -695,7 +694,6 @@
mLocationPublisher,
mMockNotificationAreaController,
mShadeExpansionStateManager,
- mock(FeatureFlagsClassic.class),
mStatusBarIconController,
mIconManagerFactory,
mCollapsedStatusBarViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 49de512..7b73528c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -549,7 +549,7 @@
@Test
fun fullscreenIsTrue_chipStillClickable() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- statusBarModeRepository.isInFullscreenMode.value = true
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
testScope.runCurrent()
assertThat(chipView.hasOnClickListeners()).isTrue()
@@ -559,7 +559,7 @@
@Test
fun callStartedInImmersiveMode_swipeGestureCallbackAdded() {
- statusBarModeRepository.isInFullscreenMode.value = true
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
testScope.runCurrent()
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -570,7 +570,7 @@
@Test
fun callStartedNotInImmersiveMode_swipeGestureCallbackNotAdded() {
- statusBarModeRepository.isInFullscreenMode.value = false
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
testScope.runCurrent()
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
@@ -583,7 +583,7 @@
fun transitionToImmersiveMode_swipeGestureCallbackAdded() {
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
- statusBarModeRepository.isInFullscreenMode.value = true
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
testScope.runCurrent()
verify(mockSwipeStatusBarAwayGestureHandler)
@@ -592,11 +592,11 @@
@Test
fun transitionOutOfImmersiveMode_swipeGestureCallbackRemoved() {
- statusBarModeRepository.isInFullscreenMode.value = true
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
notifCollectionListener.onEntryUpdated(createOngoingCallNotifEntry())
reset(mockSwipeStatusBarAwayGestureHandler)
- statusBarModeRepository.isInFullscreenMode.value = false
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false
testScope.runCurrent()
verify(mockSwipeStatusBarAwayGestureHandler)
@@ -605,7 +605,7 @@
@Test
fun callEndedWhileInImmersiveMode_swipeGestureCallbackRemoved() {
- statusBarModeRepository.isInFullscreenMode.value = true
+ statusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true
testScope.runCurrent()
val ongoingCallNotifEntry = createOngoingCallNotifEntry()
notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 688f739..09dc1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -17,49 +17,77 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import androidx.test.filters.SmallTest
+import com.android.systemui.CoroutineTestScopeModule
+import com.android.systemui.Flags
+import com.android.systemui.SysUITestComponent
+import com.android.systemui.SysUITestModule
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectValues
+import com.android.systemui.collectLastValue
+import com.android.systemui.collectValues
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.runTest
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository.Companion.DISPLAY_ID
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class CollapsedStatusBarViewModelImplTest : SysuiTestCase() {
- private lateinit var underTest: CollapsedStatusBarViewModel
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<CollapsedStatusBarViewModelImpl> {
+ val statusBarModeRepository: FakeStatusBarModeRepository
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
- private lateinit var testScope: TestScope
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ testScope: CoroutineTestScopeModule,
+ ): TestComponent
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val testComponent: TestComponent =
+ DaggerCollapsedStatusBarViewModelImplTest_TestComponent.factory()
+ .create(
+ test = this,
+ testScope = CoroutineTestScopeModule(TestScope(UnconfinedTestDispatcher())),
+ )
@Before
fun setUp() {
- testScope = TestScope(UnconfinedTestDispatcher())
-
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- val interactor =
- KeyguardTransitionInteractorFactory.create(
- scope = TestScope().backgroundScope,
- repository = keyguardTransitionRepository,
- )
- .keyguardTransitionInteractor
- underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope)
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR)
}
@Test
fun isTransitioningFromLockscreenToOccluded_started_isTrue() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -77,8 +105,8 @@
@Test
fun isTransitioningFromLockscreenToOccluded_running_isTrue() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -96,13 +124,13 @@
@Test
fun isTransitioningFromLockscreenToOccluded_finished_isFalse() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OCCLUDED,
- this.testScheduler,
+ testScope.testScheduler,
)
assertThat(underTest.isTransitioningFromLockscreenToOccluded.value).isFalse()
@@ -112,8 +140,8 @@
@Test
fun isTransitioningFromLockscreenToOccluded_canceled_isFalse() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -131,8 +159,8 @@
@Test
fun isTransitioningFromLockscreenToOccluded_irrelevantTransition_isFalse() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -150,8 +178,8 @@
@Test
fun isTransitioningFromLockscreenToOccluded_followsRepoUpdates() =
- testScope.runTest {
- val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(this)
+ testComponent.runTest {
+ val job = underTest.isTransitioningFromLockscreenToOccluded.launchIn(testScope)
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
@@ -182,7 +210,7 @@
@Test
fun transitionFromLockscreenToDreamStartedEvent_started_emitted() =
- testScope.runTest {
+ testComponent.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
keyguardTransitionRepository.sendTransitionStep(
@@ -199,7 +227,7 @@
@Test
fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() =
- testScope.runTest {
+ testComponent.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
keyguardTransitionRepository.sendTransitionStep(
@@ -234,7 +262,7 @@
@Test
fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() =
- testScope.runTest {
+ testComponent.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
keyguardTransitionRepository.sendTransitionStep(
@@ -283,7 +311,7 @@
@Test
fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() =
- testScope.runTest {
+ testComponent.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
keyguardTransitionRepository.sendTransitionStep(
@@ -300,7 +328,7 @@
@Test
fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() =
- testScope.runTest {
+ testComponent.runTest {
val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
keyguardTransitionRepository.sendTransitionStep(
@@ -317,4 +345,65 @@
assertThat(emissions).isEmpty()
}
+
+ @Test
+ fun areNotificationsLightsOut_lowProfileWithNotifications_true() =
+ testComponent.runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value =
+ StatusBarMode.LIGHTS_OUT_TRANSPARENT
+ activeNotificationListRepository.activeNotifications.value =
+ activeNotificationsStore(testNotifications)
+
+ val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() =
+ testComponent.runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value =
+ StatusBarMode.LIGHTS_OUT_TRANSPARENT
+ activeNotificationListRepository.activeNotifications.value =
+ activeNotificationsStore(emptyList())
+
+ val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() =
+ testComponent.runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+ activeNotificationListRepository.activeNotifications.value =
+ activeNotificationsStore(emptyList())
+
+ val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() =
+ testComponent.runTest {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT
+ activeNotificationListRepository.activeNotifications.value =
+ activeNotificationsStore(testNotifications)
+
+ val actual by collectLastValue(underTest.areNotificationsLightsOut(DISPLAY_ID))
+
+ assertThat(actual).isFalse()
+ }
+
+ private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) =
+ ActiveNotificationsStore.Builder()
+ .apply { notifications.forEach(::addIndividualNotif) }
+ .build()
+
+ private val testNotifications =
+ listOf(
+ activeNotificationModel(key = "notif1"),
+ activeNotificationModel(key = "notif2"),
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index 88587b2..bc50f79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -16,11 +16,20 @@
package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
+ private val areNotificationLightsOut = MutableStateFlow(false)
+
override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
+
+ override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut
+
+ fun setNotificationLightsOut(lightsOut: Boolean) {
+ areNotificationLightsOut.value = lightsOut
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index f25d282..6069083 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -16,14 +16,33 @@
package com.android.systemui.statusbar.data.repository
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.google.common.truth.Truth.assertThat
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
+@SysUISingleton
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositoryStore {
+
+ companion object {
+ const val DISPLAY_ID = Display.DEFAULT_DISPLAY
+ }
+
+ override val defaultDisplay: FakeStatusBarModePerDisplayRepository =
+ FakeStatusBarModePerDisplayRepository()
+
+ override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
+ assertThat(displayId).isEqualTo(DISPLAY_ID)
+ return defaultDisplay
+ }
+}
+
+class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository {
override val isTransientShown = MutableStateFlow(false)
override val isInFullscreenMode = MutableStateFlow(false)
override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
@@ -39,5 +58,5 @@
@Module
interface FakeStatusBarModeRepositoryModule {
- @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+ @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepositoryStore
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 5635dd5..42ab05f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -205,7 +206,10 @@
intent,
PendingIntent.FLAG_MUTABLE
| PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
- /* options= */ null, UserHandle.CURRENT);
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(), UserHandle.CURRENT);
if (sDebug) {
Slog.d(TAG, "startActivity add save UI restored with intent=" + intent);
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 215970e..e51ef29 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -864,6 +864,23 @@
}
@Override
+ public @NonNull Set<String> getAllPersistentDeviceIds() {
+ Set<String> persistentIds = new ArraySet<>();
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mActiveAssociations.size(); ++i) {
+ AssociationInfo associationInfo = mActiveAssociations.get(i);
+ if (VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(
+ associationInfo.getDeviceProfile())) {
+ persistentIds.add(
+ VirtualDeviceImpl.createPersistentDeviceId(
+ associationInfo.getId()));
+ }
+ }
+ }
+ return persistentIds;
+ }
+
+ @Override
public void registerAppsOnVirtualDeviceListener(
@NonNull AppsOnVirtualDeviceListener listener) {
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
index a570d09..6940ffe 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -69,6 +69,11 @@
}
@Override
+ public void onProcessCaptureRequest(int streamId, int frameId) throws RemoteException {
+ camera.onProcessCaptureRequest(streamId, frameId, /*metadata=*/ null);
+ }
+
+ @Override
public void onStreamClosed(int streamId) throws RemoteException {
camera.onStreamClosed(streamId);
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index ea92154..61e4f36 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -19,6 +19,9 @@
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
import static android.media.AudioSystem.isBluetoothDevice;
+import static android.media.AudioSystem.isBluetoothLeDevice;
+
+import static com.android.media.audio.Flags.dsaOverBtLeAudio;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1625,10 +1628,10 @@
}
private int getHeadSensorHandleUpdateTracker() {
- int headHandle = -1;
+ Sensor htSensor = null;
if (sRoutingDevices.isEmpty()) {
logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker");
- return headHandle;
+ return -1;
}
final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0);
List<String> deviceAddresses = mAudioService.getDeviceAddresses(currentDevice);
@@ -1642,27 +1645,86 @@
for (String address : deviceAddresses) {
UUID routingDeviceUuid = UuidUtils.uuidFromAudioDeviceAttributes(
new AudioDeviceAttributes(currentDevice.getInternalType(), address));
- for (Sensor sensor : sensors) {
- final UUID uuid = sensor.getUuid();
- if (uuid.equals(routingDeviceUuid)) {
- headHandle = sensor.getHandle();
- if (!setHasHeadTracker(currentDevice)) {
- headHandle = -1;
+ if (dsaOverBtLeAudio()) {
+ for (Sensor sensor : sensors) {
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ htSensor = sensor;
+ HeadtrackerInfo info = new HeadtrackerInfo(sensor);
+ if (isBluetoothLeDevice(currentDevice.getInternalType())) {
+ if (info.getMajorVersion() == 2) {
+ // Version 2 is used only by LE Audio profile
+ break;
+ }
+ // we do not break, as this could be a match on the A2DP sensor
+ // for a dual mode headset.
+ } else if (info.getMajorVersion() == 1) {
+ // Version 1 is used only by A2DP profile
+ break;
+ }
}
+ if (htSensor == null && uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ htSensor = sensor;
+ // we do not break, perhaps we find a head tracker on device.
+ }
+ }
+ if (htSensor != null) {
+ if (htSensor.getUuid().equals(UuidUtils.STANDALONE_UUID)) {
+ break;
+ }
+ if (setHasHeadTracker(currentDevice)) {
+ break;
+ } else {
+ htSensor = null;
+ }
+ }
+ } else {
+ for (Sensor sensor : sensors) {
+ final UUID uuid = sensor.getUuid();
+ if (uuid.equals(routingDeviceUuid)) {
+ htSensor = sensor;
+ if (!setHasHeadTracker(currentDevice)) {
+ htSensor = null;
+ }
+ break;
+ }
+ if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
+ htSensor = sensor;
+ // we do not break, perhaps we find a head tracker on device.
+ }
+ }
+ if (htSensor != null) {
break;
}
- if (uuid.equals(UuidUtils.STANDALONE_UUID)) {
- headHandle = sensor.getHandle();
- // we do not break, perhaps we find a head tracker on device.
- }
- }
- if (headHandle != -1) {
- break;
}
}
- return headHandle;
+ return htSensor != null ? htSensor.getHandle() : -1;
}
+ /**
+ * Contains the information parsed from the head tracker sensor version.
+ * See platform/hardware/libhardware/modules/sensors/dynamic_sensor/HidRawSensor.h
+ * for the definition of version and capability fields.
+ */
+ private static class HeadtrackerInfo {
+ private final int mVersion;
+ HeadtrackerInfo(Sensor sensor) {
+ mVersion = sensor.getVersion();
+ }
+ int getMajorVersion() {
+ return (mVersion & 0xFF000000) >> 24;
+ }
+ int getMinorVersion() {
+ return (mVersion & 0xFF0000) >> 16;
+ }
+ boolean hasAclTransport() {
+ return getMajorVersion() == 2 ? ((mVersion & 0x1) != 0) : false;
+ }
+ boolean hasIsoTransport() {
+ return getMajorVersion() == 2 ? ((mVersion & 0x2) != 0) : false;
+ }
+ };
+
private int getScreenSensorHandle() {
int screenHandle = -1;
Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index c629b2b..be78ea2 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -157,4 +157,10 @@
* @see VirtualDevice#getPersistentDeviceId()
*/
public abstract @Nullable String getPersistentIdForDevice(int deviceId);
+
+ /**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ */
+ public abstract @NonNull Set<String> getAllPersistentDeviceIds();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0492f43..c2b5964 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -184,6 +184,7 @@
import android.companion.ICompanionDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.LoggingOnly;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -555,7 +556,7 @@
* creation and activation of an implicit {@link android.app.AutomaticZenRule}.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L;
private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 28f656e..8b282dd3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -58,6 +58,23 @@
}
/**
+ * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+ *
+ * @see WindowProcessController#setReportedProcState(int)
+ */
+ void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // Immediately dispatching to client, and must not access WMS.
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+ if (transactionItem.isActivityLifecycleItem()) {
+ clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+ } else {
+ clientTransaction.addCallback(transactionItem);
+ }
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
* Schedules a single transaction item, either a callback or a lifecycle request, delivery to
* client application.
* @throws RemoteException
@@ -65,6 +82,7 @@
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // TODO(b/260873529): queue the transaction items.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
if (transactionItem.isActivityLifecycleItem()) {
clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
@@ -82,6 +100,7 @@
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem,
@NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+ // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
clientTransaction.addCallback(transactionItem);
clientTransaction.setLifecycleStateRequest(lifecycleItem);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 558bf9d..2b18f07 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -388,13 +388,22 @@
final IApplicationThread thread = mThread;
if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
&& thread != null && mHasCachedConfiguration) {
- final Configuration config;
+ final ConfigurationChangeItem configurationChangeItem;
synchronized (mLastReportedConfiguration) {
- config = new Configuration(mLastReportedConfiguration);
+ onConfigurationChangePreScheduled(mLastReportedConfiguration);
+ configurationChangeItem = ConfigurationChangeItem.obtain(
+ mLastReportedConfiguration, mLastTopActivityDeviceId);
}
// Schedule immediately to make sure the app component (e.g. receiver, service) can get
// the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
- scheduleConfigurationChange(thread, config);
+ try {
+ // No WM lock here.
+ mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+ thread, configurationChangeItem);
+ } catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+ + configurationChangeItem + " owner=" + mOwner, e);
+ }
}
}
@@ -1634,11 +1643,12 @@
}
}
- scheduleConfigurationChange(thread, config);
+ onConfigurationChangePreScheduled(config);
+ scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
+ config, mLastTopActivityDeviceId));
}
- private void scheduleConfigurationChange(@NonNull IApplicationThread thread,
- @NonNull Configuration config) {
+ private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1646,8 +1656,6 @@
Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
}
mHasCachedConfiguration = false;
- scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
- config, mLastTopActivityDeviceId));
}
@VisibleForTesting
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index d87b8d1..b5ba322 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -773,6 +773,30 @@
}
@Test
+ public void getAllPersistentDeviceIds_respectsCurrentAssociations() {
+ mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo));
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds())
+ .containsExactly(mDeviceImpl.getPersistentDeviceId());
+
+ mVdms.onCdmAssociationsChanged(List.of(
+ createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING),
+ createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION),
+ createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH)));
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds()).containsExactly(
+ VirtualDeviceImpl.createPersistentDeviceId(2),
+ VirtualDeviceImpl.createPersistentDeviceId(3));
+
+ mVdms.onCdmAssociationsChanged(Collections.emptyList());
+ TestableLooper.get(this).processAllMessages();
+
+ assertThat(mLocalService.getAllPersistentDeviceIds()).isEmpty();
+ }
+
+ @Test
public void onAppsOnVirtualDeviceChanged_singleVirtualDevice_listenersNotified() {
ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
mLocalService.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e152feb..e31ee11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,7 +306,7 @@
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItem(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
@@ -322,18 +322,17 @@
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
final ArgumentCaptor<ConfigurationChangeItem> captor =
ArgumentCaptor.forClass(ConfigurationChangeItem.class);
- verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), captor.capture());
+ verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+ eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
- final ArgumentCaptor<Configuration> configCaptor =
- ArgumentCaptor.forClass(Configuration.class);
- verify(client).updatePendingConfiguration(configCaptor.capture());
- assertEquals(newConfig, configCaptor.getValue());
+ verify(client).updatePendingConfiguration(newConfig);
}
@Test
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index b44f1a6..c49f8fe 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -23,10 +23,13 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,7 +56,12 @@
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
}
- transitions { testApp.finishActivity(wmHelper) }
+ transitions {
+ broadcastActionTrigger.doAction(ACTION_FINISH_ACTIVITY)
+ wmHelper.StateSyncBuilder()
+ .withActivityRemoved(ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
+ }
teardown { simpleApp.exit(wmHelper) }
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
index 976ac82..994edc5 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt
@@ -28,6 +28,7 @@
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -53,7 +54,11 @@
// Enable letterbox when the app calls setRequestedOrientation
device.executeShellCommand("cmd window set-ignore-orientation-request true")
}
- transitions { testApp.toggleFixPortraitOrientation(wmHelper) }
+ transitions {
+ broadcastActionTrigger.doAction(ACTION_TOGGLE_ORIENTATION)
+ // Ensure app relaunching transition finished and the IME was shown
+ testApp.waitIMEShown(wmHelper)
+ }
teardown {
testApp.exit()
device.executeShellCommand("cmd window set-ignore-orientation-request false")
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
index aff8e65..6ee5a9a 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt
@@ -104,7 +104,7 @@
@Presubmit
@Test
- open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
+ fun imeLayerIsVisibleWhenSwitchingToImeApp() {
flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
index 4ffdcea..1ad5c0d 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt
@@ -93,7 +93,7 @@
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
+ testApp.waitIMEShown(wmHelper)
}
}
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
index 6ad235c..181a2a2 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt
@@ -23,11 +23,14 @@
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.traces.parsers.toFlickerComponent
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
@@ -50,8 +53,12 @@
override val transition: FlickerBuilder.() -> Unit = {
setup {
testApp.launchViaIntent(wmHelper)
- wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
- testApp.startDialogThemedActivity(wmHelper)
+ testApp.waitIMEShown(wmHelper)
+ broadcastActionTrigger.doAction(ACTION_START_DIALOG_THEMED_ACTIVITY)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(
+ ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
+ .waitForAndVerify()
// Verify IME insets isn't visible on dialog since it's non-IME focusable window
assertFalse(testApp.getInsetsVisibleFromDialog(ime()))
assertTrue(testApp.getInsetsVisibleFromDialog(statusBars()))
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 7c9c05d..ad272a0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker
import android.app.Instrumentation
+import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.device.flicker.junit.FlickerBuilderProvider
@@ -50,6 +51,19 @@
/** Specification of the test transition to execute */
abstract val transition: FlickerBuilder.() -> Unit
+ protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
+
+ // Helper class to process test actions by broadcast.
+ protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) {
+ private fun createIntentWithAction(broadcastAction: String): Intent {
+ return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ }
+
+ fun doAction(broadcastAction: String) {
+ instrumentation.context.sendBroadcast(createIntentWithAction(broadcastAction))
+ }
+ }
+
/**
* Entry point for the test runner. It will use this method to initialize and cache flicker
* executions
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 252f7d3..cb1aab0 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -50,7 +50,7 @@
waitIMEShown(wmHelper)
}
- protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
+ fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
wmHelper.StateSyncBuilder().withImeShown().waitForAndVerify()
}
@@ -63,17 +63,4 @@
uiDevice.pressBack()
wmHelper.StateSyncBuilder().withImeGone().waitForAndVerify()
}
-
- open fun finishActivity(wmHelper: WindowManagerStateHelper) {
- val finishButton =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "finish_activity_btn")),
- FIND_TIMEOUT
- )
- requireNotNull(finishButton) {
- "Finish activity button not found, probably IME activity is not on the screen?"
- }
- finishButton.click()
- wmHelper.StateSyncBuilder().withActivityRemoved(this).waitForAndVerify()
- }
}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index d3cee64..0ee7aee 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -74,24 +74,6 @@
open(expectedPackage)
}
- fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
- val button =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "start_dialog_themed_activity_btn")),
- FIND_TIMEOUT
- )
-
- requireNotNull(button) {
- "Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
- }
- button.click()
- wmHelper
- .StateSyncBuilder()
- .withFullScreenApp(ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
- .waitForAndVerify()
- }
-
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
@@ -126,20 +108,4 @@
}
return false
}
-
- fun toggleFixPortraitOrientation(wmHelper: WindowManagerStateHelper) {
- val button =
- uiDevice.wait(
- Until.findObject(By.res(packageName, "toggle_fixed_portrait_btn")),
- FIND_TIMEOUT
- )
- require(button != null) {
- "Button not found, this usually happens when the device " +
- "was left in an unknown state (e.g. Screen turned off)"
- }
- button.click()
- instrumentation.waitForIdleSync()
- // Ensure app relaunching transition finish and the IME has shown
- waitIMEShown(wmHelper)
- }
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
index fa73e2c..507c1b6 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,39 +13,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
+ android:background="@android:color/holo_green_light"
android:focusableInTouchMode="true"
- android:background="@android:color/holo_green_light">
- <EditText android:id="@+id/plain_text_input"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:imeOptions="flagNoExtractUi"
- android:inputType="text"/>
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/plain_text_input"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="horizontal">
- <Button
- android:id="@+id/finish_activity_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Finish activity" />
- <Button
- android:id="@+id/start_dialog_themed_activity_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Dialog themed activity" />
- <ToggleButton
- android:id="@+id/toggle_fixed_portrait_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textOn="Portrait (On)"
- android:textOff="Portrait (Off)"
- />
- </LinearLayout>
+ android:layout_height="wrap_content"
+ android:imeOptions="flagNoExtractUi"
+ android:inputType="text" />
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 8b334c0..80c1dd0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -40,6 +40,18 @@
public static final String LABEL = "ImeActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
FLICKER_APP_PACKAGE + ".ImeActivity");
+
+ /** Intent action used to finish the test activity. */
+ public static final String ACTION_FINISH_ACTIVITY =
+ FLICKER_APP_PACKAGE + ".ImeActivity.FINISH_ACTIVITY";
+
+ /** Intent action used to start a {@link DialogThemedActivity}. */
+ public static final String ACTION_START_DIALOG_THEMED_ACTIVITY =
+ FLICKER_APP_PACKAGE + ".ImeActivity.START_DIALOG_THEMED_ACTIVITY";
+
+ /** Intent action used to toggle activity orientation. */
+ public static final String ACTION_TOGGLE_ORIENTATION =
+ FLICKER_APP_PACKAGE + ".ImeActivity.TOGGLE_ORIENTATION";
}
public static class AutoFocusActivity {
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
index d7ee2af..4418b5a 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivity.java
@@ -16,12 +16,51 @@
package com.android.server.wm.flicker.testapp;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_FINISH_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_START_DIALOG_THEMED_ACTIVITY;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Ime.Default.ACTION_TOGGLE_ORIENTATION;
+
import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Bundle;
+import android.util.Log;
import android.view.WindowManager;
-import android.widget.Button;
public class ImeActivity extends Activity {
+
+ private static final String TAG = "ImeActivity";
+
+ /** Receiver used to handle actions coming from the test helper methods. */
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_FINISH_ACTIVITY -> finish();
+ case ACTION_START_DIALOG_THEMED_ACTIVITY -> startActivity(
+ new Intent(context, DialogThemedActivity.class));
+ case ACTION_TOGGLE_ORIENTATION -> {
+ mIsPortrait = !mIsPortrait;
+ setRequestedOrientation(mIsPortrait
+ ? SCREEN_ORIENTATION_PORTRAIT
+ : SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+ default -> Log.w(TAG, "Unhandled action=" + intent.getAction());
+ }
+ }
+ };
+
+ /**
+ * Used to toggle activity orientation between portrait when {@code true} and
+ * unspecified otherwise.
+ */
+ private boolean mIsPortrait = false;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,9 +69,17 @@
.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(p);
setContentView(R.layout.activity_ime);
- Button button = findViewById(R.id.finish_activity_btn);
- button.setOnClickListener(view -> {
- finish();
- });
+
+ final var filter = new IntentFilter();
+ filter.addAction(ACTION_FINISH_ACTIVITY);
+ filter.addAction(ACTION_START_DIALOG_THEMED_ACTIVITY);
+ filter.addAction(ACTION_TOGGLE_ORIENTATION);
+ registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED);
+ }
+
+ @Override
+ protected void onDestroy() {
+ unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
}
}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
index 7ee8deb..cd711f7 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java
@@ -16,29 +16,12 @@
package com.android.server.wm.flicker.testapp;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-
-import android.content.Intent;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ToggleButton;
-
public class ImeActivityAutoFocus extends ImeActivity {
@Override
protected void onStart() {
super.onStart();
- Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn);
- startThemedActivityButton.setOnClickListener(
- button -> startActivity(new Intent(this, DialogThemedActivity.class)));
-
- ToggleButton toggleFixedPortraitButton = findViewById(R.id.toggle_fixed_portrait_btn);
- toggleFixedPortraitButton.setOnCheckedChangeListener(
- (button, isChecked) -> setRequestedOrientation(
- isChecked ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_UNSPECIFIED));
-
- EditText editTextField = findViewById(R.id.plain_text_input);
+ final var editTextField = findViewById(R.id.plain_text_input);
editTextField.requestFocus();
}
}