Merge "Add toString overrides to all virtual input objects. - VirtualInputDevice and all the relevant subclasses - VirtualInputDeviceConfig and all the relevant subclasses - All Virtual*Event" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 52200bf..cbb6566 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -63,6 +63,7 @@
":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.tracing.flags-aconfig-java{.generated_srcjars}",
+ ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
]
filegroup {
@@ -108,6 +109,11 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "telephony_flags_c_lib",
+ aconfig_declarations: "telephony_flags",
+}
+
// Window
aconfig_declarations {
name: "com.android.window.flags.window-aconfig",
@@ -220,6 +226,7 @@
name: "android.security.flags-aconfig-java-host",
aconfig_declarations: "android.security.flags-aconfig",
host_supported: true,
+ mode: "test",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -249,6 +256,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.os.flags-aconfig-java-host",
+ aconfig_declarations: "android.os.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// VirtualDeviceManager
java_aconfig_library {
name: "android.companion.virtual.flags-aconfig-java",
@@ -722,3 +736,16 @@
aconfig_declarations: "android.tracing.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// App Widgets
+aconfig_declarations {
+ name: "android.appwidget.flags-aconfig",
+ package: "android.appwidget.flags",
+ srcs: ["core/java/android/appwidget/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.appwidget.flags-aconfig-java",
+ aconfig_declarations: "android.appwidget.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index e02ba77..44b2f38 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -5,3 +5,5 @@
prabirmsp@google.com
svv@google.com
vdevmurari@google.com
+
+per-file Virtual*=file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline
index 29a8dfa..a4174ee 100644
--- a/api/javadoc-lint-baseline
+++ b/api/javadoc-lint-baseline
@@ -1,13 +1,3 @@
-// b/305195721
-android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag "android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101]
-android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101]
-
// b/303477132
android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101]
android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101]
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 9d8f1f4..01486c0 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -16,19 +16,10 @@
package com.android.commands.uinput;
-import android.util.JsonReader;
-import android.util.JsonToken;
-import android.util.Log;
import android.util.SparseArray;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.IntStream;
import src.com.android.commands.uinput.InputAbsInfo;
@@ -40,46 +31,44 @@
private static final String TAG = "UinputEvent";
enum Command {
- REGISTER("register"),
- DELAY("delay"),
- INJECT("inject"),
- SYNC("sync");
-
- final String mCommandName;
-
- Command(String command) {
- mCommandName = command;
- }
+ REGISTER,
+ DELAY,
+ INJECT,
+ SYNC,
}
- private static final int EV_KEY = 0x01;
- private static final int EV_REL = 0x02;
- private static final int EV_ABS = 0x03;
- private static final int EV_MSC = 0x04;
- private static final int EV_SW = 0x05;
- private static final int EV_LED = 0x11;
- private static final int EV_SND = 0x12;
- private static final int EV_FF = 0x15;
+ // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the
+ // kernel.
+ public static final int EV_KEY = 0x01;
+ public static final int EV_REL = 0x02;
+ public static final int EV_ABS = 0x03;
+ public static final int EV_MSC = 0x04;
+ public static final int EV_SW = 0x05;
+ public static final int EV_LED = 0x11;
+ public static final int EV_SND = 0x12;
+ public static final int EV_FF = 0x15;
- private enum UinputControlCode {
- UI_SET_EVBIT("UI_SET_EVBIT", 100),
- UI_SET_KEYBIT("UI_SET_KEYBIT", 101),
- UI_SET_RELBIT("UI_SET_RELBIT", 102),
- UI_SET_ABSBIT("UI_SET_ABSBIT", 103),
- UI_SET_MSCBIT("UI_SET_MSCBIT", 104),
- UI_SET_LEDBIT("UI_SET_LEDBIT", 105),
- UI_SET_SNDBIT("UI_SET_SNDBIT", 106),
- UI_SET_FFBIT("UI_SET_FFBIT", 107),
- UI_SET_SWBIT("UI_SET_SWBIT", 109),
- UI_SET_PROPBIT("UI_SET_PROPBIT", 110);
+ public enum UinputControlCode {
+ UI_SET_EVBIT(100),
+ UI_SET_KEYBIT(101),
+ UI_SET_RELBIT(102),
+ UI_SET_ABSBIT(103),
+ UI_SET_MSCBIT(104),
+ UI_SET_LEDBIT(105),
+ UI_SET_SNDBIT(106),
+ UI_SET_FFBIT(107),
+ UI_SET_SWBIT(109),
+ UI_SET_PROPBIT(110);
- final String mName;
- final int mValue;
+ private final int mValue;
- UinputControlCode(String name, int value) {
- this.mName = name;
+ UinputControlCode(int value) {
this.mValue = value;
}
+
+ public int getValue() {
+ return mValue;
+ }
}
// These constants come from "include/uapi/linux/input.h" in the kernel
@@ -104,9 +93,9 @@
private Bus mBus;
private int[] mInjections;
private SparseArray<int[]> mConfiguration;
- private int mDuration;
+ private int mDurationMillis;
private int mFfEffectsMax = 0;
- private String mInputport;
+ private String mInputPort;
private SparseArray<InputAbsInfo> mAbsInfo;
private String mSyncToken;
@@ -138,12 +127,20 @@
return mInjections;
}
+ /**
+ * Returns a {@link SparseArray} describing the event codes that should be registered for the
+ * device. The keys are uinput ioctl codes (such as those returned from {@link
+ * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
+ * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
+ * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
+ * ({@code BTN_MIDDLE}).
+ */
public SparseArray<int[]> getConfiguration() {
return mConfiguration;
}
- public int getDuration() {
- return mDuration;
+ public int getDurationMillis() {
+ return mDurationMillis;
}
public int getFfEffectsMax() {
@@ -155,7 +152,7 @@
}
public String getPort() {
- return mInputport;
+ return mInputPort;
}
public String getSyncToken() {
@@ -174,13 +171,13 @@
+ ", bus=" + mBus
+ ", events=" + Arrays.toString(mInjections)
+ ", configuration=" + mConfiguration
- + ", duration=" + mDuration
+ + ", duration=" + mDurationMillis + "ms"
+ ", ff_effects_max=" + mFfEffectsMax
- + ", port=" + mInputport
+ + ", port=" + mInputPort
+ "}";
}
- private static class Builder {
+ public static class Builder {
private Event mEvent;
Builder() {
@@ -191,15 +188,8 @@
mEvent.mId = id;
}
- private void setCommand(String command) {
- Objects.requireNonNull(command, "Command must not be null");
- for (Command cmd : Command.values()) {
- if (cmd.mCommandName.equals(command)) {
- mEvent.mCommand = cmd;
- return;
- }
- }
- throw new IllegalStateException("Unrecognized command: " + command);
+ public void setCommand(String command) {
+ mEvent.mCommand = Command.valueOf(command.toUpperCase());
}
public void setName(String name) {
@@ -210,6 +200,12 @@
mEvent.mInjections = events;
}
+ /**
+ * Sets the event codes that should be registered with a {@code register} command.
+ *
+ * @param configuration An array of ioctls and event codes, as described at
+ * {@link Event#getConfiguration()}.
+ */
public void setConfiguration(SparseArray<int[]> configuration) {
mEvent.mConfiguration = configuration;
}
@@ -226,8 +222,8 @@
mEvent.mBus = bus;
}
- public void setDuration(int duration) {
- mEvent.mDuration = duration;
+ public void setDurationMillis(int durationMillis) {
+ mEvent.mDurationMillis = durationMillis;
}
public void setFfEffectsMax(int ffEffectsMax) {
@@ -238,8 +234,8 @@
mEvent.mAbsInfo = absInfo;
}
- public void setInputport(String port) {
- mEvent.mInputport = port;
+ public void setInputPort(String port) {
+ mEvent.mInputPort = port;
}
public void setSyncToken(String syncToken) {
@@ -260,7 +256,7 @@
}
}
case DELAY -> {
- if (mEvent.mDuration <= 0) {
+ if (mEvent.mDurationMillis <= 0) {
throw new IllegalStateException("Delay has missing or invalid duration");
}
}
@@ -278,343 +274,4 @@
return mEvent;
}
}
-
- /**
- * A class that parses the JSON event format from an input stream to build device events.
- */
- public static class Reader {
- private JsonReader mReader;
-
- public Reader(InputStreamReader in) {
- mReader = new JsonReader(in);
- mReader.setLenient(true);
- }
-
- /**
- * Get next event entry from JSON file reader.
- */
- public Event getNextEvent() throws IOException {
- Event e = null;
- while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
- Event.Builder eb = new Event.Builder();
- try {
- mReader.beginObject();
- while (mReader.hasNext()) {
- String name = mReader.nextName();
- switch (name) {
- case "id":
- eb.setId(readInt());
- break;
- case "command":
- eb.setCommand(mReader.nextString());
- break;
- case "name":
- eb.setName(mReader.nextString());
- break;
- case "vid":
- eb.setVid(readInt());
- break;
- case "pid":
- eb.setPid(readInt());
- break;
- case "bus":
- eb.setBus(readBus());
- break;
- case "events":
- int[] injections = readInjectedEvents().stream()
- .mapToInt(Integer::intValue).toArray();
- eb.setInjections(injections);
- break;
- case "configuration":
- eb.setConfiguration(readConfiguration());
- break;
- case "ff_effects_max":
- eb.setFfEffectsMax(readInt());
- break;
- case "abs_info":
- eb.setAbsInfo(readAbsInfoArray());
- break;
- case "duration":
- eb.setDuration(readInt());
- break;
- case "port":
- eb.setInputport(mReader.nextString());
- break;
- case "syncToken":
- eb.setSyncToken(mReader.nextString());
- break;
- default:
- mReader.skipValue();
- }
- }
- mReader.endObject();
- } catch (IllegalStateException ex) {
- error("Error reading in object, ignoring.", ex);
- consumeRemainingElements();
- mReader.endObject();
- continue;
- }
- e = eb.build();
- }
-
- return e;
- }
-
- private ArrayList<Integer> readInjectedEvents() throws IOException {
- ArrayList<Integer> data = new ArrayList<>();
- try {
- mReader.beginArray();
- while (mReader.hasNext()) {
- // Read events in groups of three, because we expect an event type, event code,
- // and event value.
- final int type = readEvdevEventType();
- data.add(type);
- data.add(readEvdevEventCode(type));
- data.add(readInt());
- }
- mReader.endArray();
- } catch (IllegalStateException | NumberFormatException e) {
- consumeRemainingElements();
- mReader.endArray();
- throw new IllegalStateException("Encountered malformed data.", e);
- }
- return data;
- }
-
- private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
- switch (mReader.peek()) {
- case NUMBER: {
- return mReader.nextInt();
- }
- case STRING: {
- final var str = mReader.nextString();
- try {
- // Attempt to first parse the value as an int.
- return Integer.decode(str);
- } catch (NumberFormatException e) {
- // Then fall back to the supplied function.
- return stringToInt.apply(str);
- }
- }
- default: {
- throw new IllegalStateException(
- "Encountered malformed data. Expected int or string.");
- }
- }
- }
-
- private int readInt() throws IOException {
- return readValueAsInt((str) -> {
- throw new IllegalStateException("Encountered malformed data. Expected int.");
- });
- }
-
- private Bus readBus() throws IOException {
- String val = mReader.nextString();
- return Bus.valueOf(val.toUpperCase());
- }
-
- private SparseArray<int[]> readConfiguration()
- throws IllegalStateException, IOException {
- SparseArray<int[]> configuration = new SparseArray<>();
- try {
- mReader.beginArray();
- while (mReader.hasNext()) {
- UinputControlCode controlCode = null;
- IntStream data = null;
- mReader.beginObject();
- while (mReader.hasNext()) {
- String name = mReader.nextName();
- switch (name) {
- case "type":
- controlCode = readUinputControlCode();
- break;
- case "data":
- Objects.requireNonNull(controlCode,
- "Configuration 'type' must be specified before 'data'.");
- data = readDataForControlCode(controlCode)
- .stream().mapToInt(Integer::intValue);
- break;
- default:
- consumeRemainingElements();
- mReader.endObject();
- throw new IllegalStateException(
- "Invalid key in device configuration: " + name);
- }
- }
- mReader.endObject();
- if (controlCode != null && data != null) {
- final int[] existing = configuration.get(controlCode.mValue);
- configuration.put(controlCode.mValue, existing == null ? data.toArray()
- : IntStream.concat(IntStream.of(existing), data).toArray());
- }
- }
- mReader.endArray();
- } catch (IllegalStateException | NumberFormatException e) {
- consumeRemainingElements();
- mReader.endArray();
- throw new IllegalStateException("Encountered malformed data.", e);
- }
- return configuration;
- }
-
- private UinputControlCode readUinputControlCode() throws IOException {
- var code = readValueAsInt((controlTypeStr) -> {
- for (UinputControlCode controlCode : UinputControlCode.values()) {
- if (controlCode.mName.equals(controlTypeStr)) {
- return controlCode.mValue;
- }
- }
- return -1;
- });
- for (UinputControlCode controlCode : UinputControlCode.values()) {
- if (controlCode.mValue == code) {
- return controlCode;
- }
- }
- return null;
- }
-
- private List<Integer> readDataForControlCode(
- UinputControlCode controlCode) throws IOException {
- return switch (controlCode) {
- case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
- case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_KEY));
- case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_REL));
- case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_ABS));
- case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_MSC));
- case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_LED));
- case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SND));
- case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_FF));
- case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SW));
- case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp);
- };
- }
-
- interface IntValueReader {
- int readNextValue() throws IOException;
- }
-
- private ArrayList<Integer> readArrayAsInts(
- IntValueReader nextValueReader) throws IOException {
- ArrayList<Integer> data = new ArrayList<>();
- try {
- mReader.beginArray();
- while (mReader.hasNext()) {
- data.add(nextValueReader.readNextValue());
- }
- mReader.endArray();
- } catch (IllegalStateException | NumberFormatException e) {
- consumeRemainingElements();
- mReader.endArray();
- throw new IllegalStateException("Encountered malformed data.", e);
- }
- return data;
- }
-
- private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
- InputAbsInfo absInfo = new InputAbsInfo();
- try {
- mReader.beginObject();
- while (mReader.hasNext()) {
- String name = mReader.nextName();
- switch (name) {
- case "value":
- absInfo.value = readInt();
- break;
- case "minimum":
- absInfo.minimum = readInt();
- break;
- case "maximum":
- absInfo.maximum = readInt();
- break;
- case "fuzz":
- absInfo.fuzz = readInt();
- break;
- case "flat":
- absInfo.flat = readInt();
- break;
- case "resolution":
- absInfo.resolution = readInt();
- break;
- default:
- consumeRemainingElements();
- mReader.endObject();
- throw new IllegalStateException("Invalid key in abs info: " + name);
- }
- }
- mReader.endObject();
- } catch (IllegalStateException | NumberFormatException e) {
- consumeRemainingElements();
- mReader.endObject();
- throw new IllegalStateException("Encountered malformed data.", e);
- }
- return absInfo;
- }
-
- private SparseArray<InputAbsInfo> readAbsInfoArray()
- throws IllegalStateException, IOException {
- SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
- try {
- mReader.beginArray();
- while (mReader.hasNext()) {
- int type = 0;
- InputAbsInfo absInfo = null;
- mReader.beginObject();
- while (mReader.hasNext()) {
- String name = mReader.nextName();
- switch (name) {
- case "code":
- type = readEvdevEventCode(EV_ABS);
- break;
- case "info":
- absInfo = readAbsInfo();
- break;
- default:
- consumeRemainingElements();
- mReader.endObject();
- throw new IllegalStateException("Invalid key in abs info array: "
- + name);
- }
- }
- mReader.endObject();
- if (absInfo != null) {
- infoArray.put(type, absInfo);
- }
- }
- mReader.endArray();
- } catch (IllegalStateException | NumberFormatException e) {
- consumeRemainingElements();
- mReader.endArray();
- throw new IllegalStateException("Encountered malformed data.", e);
- }
- return infoArray;
- }
-
- private int readEvdevEventType() throws IOException {
- return readValueAsInt(Device::getEvdevEventTypeByLabel);
- }
-
- private int readEvdevEventCode(int type) throws IOException {
- return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str));
- }
-
- private int readEvdevInputProp() throws IOException {
- return readValueAsInt(Device::getEvdevInputPropByLabel);
- }
-
- private void consumeRemainingElements() throws IOException {
- while (mReader.hasNext()) {
- mReader.skipValue();
- }
- }
- }
-
- private static void error(String msg, Exception e) {
- System.out.println(msg);
- Log.e(TAG, msg);
- if (e != null) {
- Log.e(TAG, Log.getStackTraceString(e));
- }
- }
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
new file mode 100644
index 0000000..53d0be8
--- /dev/null
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -0,0 +1,333 @@
+/*
+ * 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.util.JsonReader;
+import android.util.JsonToken;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+import src.com.android.commands.uinput.InputAbsInfo;
+
+/**
+ * A class that parses the JSON-like event format described in the README to build {@link Event}s.
+ */
+public class JsonStyleParser {
+ private static final String TAG = "UinputJsonStyleParser";
+
+ private JsonReader mReader;
+
+ public JsonStyleParser(InputStreamReader in) {
+ mReader = new JsonReader(in);
+ mReader.setLenient(true);
+ }
+
+ /**
+ * Gets the next event entry from the JSON file.
+ */
+ public Event getNextEvent() throws IOException {
+ Event e = null;
+ while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) {
+ Event.Builder eb = new Event.Builder();
+ try {
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "id" -> eb.setId(readInt());
+ case "command" -> eb.setCommand(mReader.nextString());
+ case "name" -> eb.setName(mReader.nextString());
+ case "vid" -> eb.setVid(readInt());
+ case "pid" -> eb.setPid(readInt());
+ case "bus" -> eb.setBus(readBus());
+ case "events" -> {
+ int[] injections = readInjectedEvents().stream()
+ .mapToInt(Integer::intValue).toArray();
+ eb.setInjections(injections);
+ }
+ case "configuration" -> eb.setConfiguration(readConfiguration());
+ case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
+ case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
+ case "duration" -> eb.setDurationMillis(readInt());
+ case "port" -> eb.setInputPort(mReader.nextString());
+ case "syncToken" -> eb.setSyncToken(mReader.nextString());
+ default -> mReader.skipValue();
+ }
+ }
+ mReader.endObject();
+ } catch (IllegalStateException ex) {
+ error("Error reading in object, ignoring.", ex);
+ consumeRemainingElements();
+ mReader.endObject();
+ continue;
+ }
+ e = eb.build();
+ }
+
+ return e;
+ }
+
+ private ArrayList<Integer> readInjectedEvents() throws IOException {
+ ArrayList<Integer> data = new ArrayList<>();
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ // Read events in groups of three, because we expect an event type, event code,
+ // and event value.
+ final int type = readEvdevEventType();
+ data.add(type);
+ data.add(readEvdevEventCode(type));
+ data.add(readInt());
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return data;
+ }
+
+ private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
+ switch (mReader.peek()) {
+ case NUMBER: {
+ return mReader.nextInt();
+ }
+ case STRING: {
+ final var str = mReader.nextString();
+ try {
+ // Attempt to first parse the value as an int.
+ return Integer.decode(str);
+ } catch (NumberFormatException e) {
+ // Then fall back to the supplied function.
+ return stringToInt.apply(str);
+ }
+ }
+ default: {
+ throw new IllegalStateException(
+ "Encountered malformed data. Expected int or string.");
+ }
+ }
+ }
+
+ private int readInt() throws IOException {
+ return readValueAsInt((str) -> {
+ throw new IllegalStateException("Encountered malformed data. Expected int.");
+ });
+ }
+
+ private Event.Bus readBus() throws IOException {
+ String val = mReader.nextString();
+ return Event.Bus.valueOf(val.toUpperCase());
+ }
+
+ private SparseArray<int[]> readConfiguration()
+ throws IllegalStateException, IOException {
+ SparseArray<int[]> configuration = new SparseArray<>();
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ Event.UinputControlCode controlCode = null;
+ IntStream data = null;
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "type":
+ controlCode = readUinputControlCode();
+ break;
+ case "data":
+ Objects.requireNonNull(controlCode,
+ "Configuration 'type' must be specified before 'data'.");
+ data = readDataForControlCode(controlCode)
+ .stream().mapToInt(Integer::intValue);
+ break;
+ default:
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException(
+ "Invalid key in device configuration: " + name);
+ }
+ }
+ mReader.endObject();
+ if (controlCode != null && data != null) {
+ final int[] existing = configuration.get(controlCode.getValue());
+ configuration.put(controlCode.getValue(), existing == null ? data.toArray()
+ : IntStream.concat(IntStream.of(existing), data).toArray());
+ }
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return configuration;
+ }
+
+ private Event.UinputControlCode readUinputControlCode() throws IOException {
+ var code = readValueAsInt((controlTypeStr) -> {
+ try {
+ return Event.UinputControlCode.valueOf(controlTypeStr).getValue();
+ } catch (IllegalArgumentException ex) {
+ return -1;
+ }
+ });
+ for (Event.UinputControlCode controlCode : Event.UinputControlCode.values()) {
+ if (controlCode.getValue() == code) {
+ return controlCode;
+ }
+ }
+ return null;
+ }
+
+ private List<Integer> readDataForControlCode(
+ Event.UinputControlCode controlCode) throws IOException {
+ return switch (controlCode) {
+ case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
+ case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_KEY));
+ case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_REL));
+ case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_ABS));
+ case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_MSC));
+ case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_LED));
+ case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SND));
+ case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_FF));
+ case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SW));
+ case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp);
+ };
+ }
+
+ interface IntValueReader {
+ int readNextValue() throws IOException;
+ }
+
+ private ArrayList<Integer> readArrayAsInts(
+ IntValueReader nextValueReader) throws IOException {
+ ArrayList<Integer> data = new ArrayList<>();
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ data.add(nextValueReader.readNextValue());
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return data;
+ }
+
+ private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
+ InputAbsInfo absInfo = new InputAbsInfo();
+ try {
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "value" -> absInfo.value = readInt();
+ case "minimum" -> absInfo.minimum = readInt();
+ case "maximum" -> absInfo.maximum = readInt();
+ case "fuzz" -> absInfo.fuzz = readInt();
+ case "flat" -> absInfo.flat = readInt();
+ case "resolution" -> absInfo.resolution = readInt();
+ default -> {
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException("Invalid key in abs info: " + name);
+ }
+ }
+ }
+ mReader.endObject();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return absInfo;
+ }
+
+ private SparseArray<InputAbsInfo> readAbsInfoArray()
+ throws IllegalStateException, IOException {
+ SparseArray<InputAbsInfo> infoArray = new SparseArray<>();
+ try {
+ mReader.beginArray();
+ while (mReader.hasNext()) {
+ int type = 0;
+ InputAbsInfo absInfo = null;
+ mReader.beginObject();
+ while (mReader.hasNext()) {
+ String name = mReader.nextName();
+ switch (name) {
+ case "code" -> type = readEvdevEventCode(Event.EV_ABS);
+ case "info" -> absInfo = readAbsInfo();
+ default -> {
+ consumeRemainingElements();
+ mReader.endObject();
+ throw new IllegalStateException("Invalid key in abs info array: "
+ + name);
+ }
+ }
+ }
+ mReader.endObject();
+ if (absInfo != null) {
+ infoArray.put(type, absInfo);
+ }
+ }
+ mReader.endArray();
+ } catch (IllegalStateException | NumberFormatException e) {
+ consumeRemainingElements();
+ mReader.endArray();
+ throw new IllegalStateException("Encountered malformed data.", e);
+ }
+ return infoArray;
+ }
+
+ private int readEvdevEventType() throws IOException {
+ return readValueAsInt(Device::getEvdevEventTypeByLabel);
+ }
+
+ private int readEvdevEventCode(int type) throws IOException {
+ return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str));
+ }
+
+ private int readEvdevInputProp() throws IOException {
+ return readValueAsInt(Device::getEvdevInputPropByLabel);
+ }
+
+ private void consumeRemainingElements() throws IOException {
+ while (mReader.hasNext()) {
+ mReader.skipValue();
+ }
+ }
+
+ private static void error(String msg, Exception e) {
+ System.out.println(msg);
+ Log.e(TAG, msg);
+ if (e != null) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ }
+ }
+}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 47b7a354..fe76abb 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -35,7 +35,7 @@
public class Uinput {
private static final String TAG = "UINPUT";
- private final Event.Reader mReader;
+ private final JsonStyleParser mParser;
private final SparseArray<Device> mDevices;
private static void usage() {
@@ -74,7 +74,7 @@
private Uinput(InputStream in) {
mDevices = new SparseArray<Device>();
try {
- mReader = new Event.Reader(new InputStreamReader(in, "UTF-8"));
+ mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
@@ -83,7 +83,7 @@
private void run() {
try {
Event e = null;
- while ((e = mReader.getNextEvent()) != null) {
+ while ((e = mParser.getNextEvent()) != null) {
process(e);
}
} catch (IOException ex) {
@@ -111,7 +111,7 @@
case REGISTER ->
error("Device id=" + e.getId() + " is already registered. Ignoring event.");
case INJECT -> d.injectEvent(e.getInjections());
- case DELAY -> d.addDelay(e.getDuration());
+ case DELAY -> d.addDelay(e.getDurationMillis());
case SYNC -> d.syncEvent(e.getSyncToken());
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 55933d43..4256d51 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5316,21 +5316,23 @@
method public android.net.Uri getConditionId();
method @Nullable public android.content.ComponentName getConfigurationActivity();
method public long getCreationTime();
+ method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects();
method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId();
method public int getInterruptionFilter();
method public String getName();
method public android.content.ComponentName getOwner();
method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription();
method @FlaggedApi("android.app.modes_api") public int getType();
- method public android.service.notification.ZenPolicy getZenPolicy();
+ method @Nullable public android.service.notification.ZenPolicy getZenPolicy();
method public boolean isEnabled();
method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed();
method public void setConditionId(android.net.Uri);
method public void setConfigurationActivity(@Nullable android.content.ComponentName);
+ method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
method public void setEnabled(boolean);
method public void setInterruptionFilter(int);
method public void setName(String);
- method public void setZenPolicy(android.service.notification.ZenPolicy);
+ method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR;
field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3
@@ -5350,6 +5352,7 @@
method @NonNull public android.app.AutomaticZenRule build();
method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri);
method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName);
+ method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean);
method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int);
method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int);
@@ -7861,6 +7864,7 @@
field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity";
field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken";
field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled";
+ field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages";
}
@@ -22804,11 +22808,11 @@
method public void clearOnSessionLostStateListener();
method public void close();
method public void closeSession(@NonNull byte[]);
- method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel();
+ method public int getConnectedHdcpLevel();
method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String);
method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException;
method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages();
- method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel();
+ method public int getMaxHdcpLevel();
method public static int getMaxSecurityLevel();
method public int getMaxSessionCount();
method public android.os.PersistableBundle getMetrics();
@@ -22822,13 +22826,13 @@
method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]);
method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds();
method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops();
- method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]);
+ method public int getSecurityLevel(@NonNull byte[]);
method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes();
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID);
method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String);
- method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int);
+ method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, int);
method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException;
- method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
+ method @NonNull public byte[] openSession(int) throws android.media.NotProvisionedException, android.media.ResourceBusyException;
method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException;
method public void provideProvisionResponse(@NonNull byte[]) throws android.media.DeniedByServerException;
method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]);
@@ -22840,7 +22844,7 @@
method public void removeOfflineLicense(@NonNull byte[]);
method @Deprecated public void removeSecureStop(@NonNull byte[]);
method public boolean requiresSecureDecoder(@NonNull String);
- method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int);
+ method public boolean requiresSecureDecoder(@NonNull String, int);
method public void restoreKeys(@NonNull byte[], @NonNull byte[]);
method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener);
method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler);
@@ -22929,9 +22933,6 @@
field public static final int ERROR_ZERO_SUBSAMPLES = 33; // 0x21
}
- @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel {
- }
-
public static final class MediaDrm.KeyRequest {
method @NonNull public byte[] getData();
method @NonNull public String getDefaultUrl();
@@ -23033,9 +23034,6 @@
method @NonNull public String getDefaultUrl();
}
- @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel {
- }
-
public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable {
ctor public MediaDrm.SessionException(int, @Nullable String);
method @Deprecated public int getErrorCode();
@@ -33152,6 +33150,7 @@
public static class PerformanceHintManager.Session implements java.io.Closeable {
method public void close();
method public void reportActualWorkDuration(long);
+ method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration);
method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean);
method public void setThreads(@NonNull int[]);
method public void updateTargetWorkDuration(long);
@@ -33493,6 +33492,7 @@
method public static boolean setCurrentTimeMillis(long);
method public static void sleep(long);
method public static long uptimeMillis();
+ method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos();
}
public class TestLooperManager {
@@ -33758,6 +33758,22 @@
method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes);
}
+ @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable {
+ ctor public WorkDuration();
+ ctor public WorkDuration(long, long, long, long);
+ method public int describeContents();
+ method public long getActualCpuDurationNanos();
+ method public long getActualGpuDurationNanos();
+ method public long getActualTotalDurationNanos();
+ method public long getWorkPeriodStartTimestampNanos();
+ method public void setActualCpuDurationNanos(long);
+ method public void setActualGpuDurationNanos(long);
+ method public void setActualTotalDurationNanos(long);
+ method public void setWorkPeriodStartTimestampNanos(long);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR;
+ }
+
public class WorkSource implements android.os.Parcelable {
ctor public WorkSource();
ctor public WorkSource(android.os.WorkSource);
@@ -40717,6 +40733,26 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
}
+ @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean shouldDimWallpaper();
+ method public boolean shouldDisplayGrayscale();
+ method public boolean shouldSuppressAmbientDisplay();
+ method public boolean shouldUseNightMode();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR;
+ }
+
+ @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+ ctor public ZenDeviceEffects.Builder();
+ ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects);
+ method @NonNull public android.service.notification.ZenDeviceEffects build();
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean);
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean);
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean);
+ method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean);
+ }
+
public final class ZenPolicy implements android.os.Parcelable {
method public int describeContents();
method public int getPriorityCallSenders();
@@ -43671,6 +43707,7 @@
field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int";
field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array";
field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_gcm_key_size_int_array";
field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int";
@@ -43686,12 +43723,15 @@
field public static final String KEY_IKE_REMOTE_ID_TYPE_INT = "iwlan.ike_remote_id_type_int";
field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array";
field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_ctr_key_size_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_gcm_key_size_int_array";
field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int";
field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array";
field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int";
field public static final String KEY_PREFIX = "iwlan.";
field public static final String KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY = "iwlan.retransmit_timer_sec_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_aead_algorithms_int_array";
field public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_encryption_algorithms_int_array";
+ field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_aead_algorithms_int_array";
field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array";
field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array";
field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array";
@@ -44715,10 +44755,16 @@
method public int getLastCauseCode();
method @Nullable public android.net.LinkProperties getLinkProperties();
method public int getNetworkType();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus();
method public int getState();
method public int getTransportType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+ field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4
+ field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3
+ field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0
}
public final class RadioAccessSpecifier implements android.os.Parcelable {
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 5a4be65..989bb77 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -224,6 +224,12 @@
ctor public AudioFormat();
}
+ @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel {
+ }
+
+ @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel {
+ }
+
}
package android.media.tv {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5a8209f..a7ea753 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3331,6 +3331,53 @@
}
+package android.companion.virtual.camera {
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCamera implements java.io.Closeable {
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig getConfig();
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback {
+ method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata);
+ method public void onStreamClosed(int);
+ method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig);
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @StringRes public int getDisplayNameStringRes();
+ method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR;
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
+ ctor public VirtualCameraConfig.Builder();
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int);
+ method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR;
+ }
+
+ @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable {
+ ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int);
+ method public int describeContents();
+ method public int getFormat();
+ method @IntRange(from=1) public int getHeight();
+ method @IntRange(from=1) public int getWidth();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR;
+ }
+
+}
+
package android.companion.virtual.sensor {
public final class VirtualSensor implements android.os.Parcelable {
@@ -4763,7 +4810,7 @@
method public void onChange(@NonNull String);
}
- @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+ @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult {
}
public static interface HdmiControlManager.HotplugEventListener {
@@ -14790,6 +14837,7 @@
method @Deprecated public int getMtu();
method public int getMtuV4();
method public int getMtuV6();
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus();
method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses();
method public int getPduSessionId();
method public int getProtocolType();
@@ -14826,6 +14874,7 @@
method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int);
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>);
method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int);
method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int);
@@ -14905,6 +14954,7 @@
method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback);
@@ -14966,6 +15016,7 @@
method public final int getSlotIndex();
method public void reportEmergencyDataNetworkPreferredTransportChanged(int);
method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>);
+ method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8b20720..75797ed 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,6 @@
field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
- field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
}
public class DevicePolicyManager {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 3f9cc65..8ad6ea2 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -632,6 +632,7 @@
InputDevice.SOURCE_JOYSTICK,
InputDevice.SOURCE_SENSOR
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface MotionEventSources {}
/**
diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java
index af00f31..6ec956ee 100644
--- a/core/java/android/accessibilityservice/TouchInteractionController.java
+++ b/core/java/android/accessibilityservice/TouchInteractionController.java
@@ -24,6 +24,8 @@
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityInteractionClient;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executor;
@@ -92,6 +94,7 @@
STATE_DRAGGING,
STATE_DELEGATING
})
+ @Retention(RetentionPolicy.SOURCE)
private @interface State {}
// The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index be433d2..ed18d81 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -23,7 +23,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
+
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+
import static java.lang.Character.MIN_VALUE;
import android.annotation.AnimRes;
@@ -1000,6 +1002,7 @@
FULLSCREEN_MODE_REQUEST_EXIT,
FULLSCREEN_MODE_REQUEST_ENTER
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FullscreenModeRequest {}
/** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the
@@ -1016,6 +1019,7 @@
OVERRIDE_TRANSITION_OPEN,
OVERRIDE_TRANSITION_CLOSE
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface OverrideTransition {}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8b4ebae..854e121 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4998,6 +4998,7 @@
STOP_USER_ON_SWITCH_TRUE,
STOP_USER_ON_SWITCH_FALSE
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface StopUserOnSwitch {}
/**
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 919e084..a7b29aa 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -22,12 +22,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationManager.InterruptionFilter;
-import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
import android.view.WindowInsetsController;
@@ -111,6 +111,7 @@
private ComponentName configurationActivity;
private long creationTime;
private ZenPolicy mZenPolicy;
+ private ZenDeviceEffects mDeviceEffects;
private boolean mModified = false;
private String mPkg;
private int mType = TYPE_UNKNOWN;
@@ -190,6 +191,7 @@
/**
* @hide
*/
+ // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder).
public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
long creationTime) {
@@ -209,10 +211,11 @@
configurationActivity = getTrimmedComponentName(
source.readParcelable(null, android.content.ComponentName.class));
creationTime = source.readLong();
- mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+ mZenPolicy = source.readParcelable(null, ZenPolicy.class);
mModified = source.readInt() == ENABLED;
mPkg = source.readString();
if (Flags.modesApi()) {
+ mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
mAllowManualInvocation = source.readBoolean();
mIconResId = source.readInt();
mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
@@ -274,10 +277,18 @@
/**
* Gets the zen policy.
*/
+ @Nullable
public ZenPolicy getZenPolicy() {
return mZenPolicy == null ? null : this.mZenPolicy.copy();
}
+ /** Gets the {@link ZenDeviceEffects} of this rule. */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public ZenDeviceEffects getDeviceEffects() {
+ return mDeviceEffects;
+ }
+
/**
* Returns the time this rule was created, represented as milliseconds since the epoch.
*/
@@ -325,11 +336,21 @@
/**
* Sets the zen policy.
*/
- public void setZenPolicy(ZenPolicy zenPolicy) {
+ public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
}
/**
+ * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to
+ * the device behavior that should apply while the rule is active, but are not directly related
+ * to suppressing notifications (for example: disabling always-on display).
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mDeviceEffects = deviceEffects;
+ }
+
+ /**
* Sets the configuration activity - an activity that handles
* {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
* about this rule and/or allows them to configure it. This is required to be non-null for rules
@@ -451,6 +472,7 @@
dest.writeInt(mModified ? ENABLED : DISABLED);
dest.writeString(mPkg);
if (Flags.modesApi()) {
+ dest.writeParcelable(mDeviceEffects, 0);
dest.writeBoolean(mAllowManualInvocation);
dest.writeInt(mIconResId);
dest.writeString(mTriggerDescription);
@@ -472,7 +494,8 @@
.append(",mZenPolicy=").append(mZenPolicy);
if (Flags.modesApi()) {
- sb.append(",allowManualInvocation=").append(mAllowManualInvocation)
+ sb.append(",deviceEffects=").append(mDeviceEffects)
+ .append(",allowManualInvocation=").append(mAllowManualInvocation)
.append(",iconResId=").append(mIconResId)
.append(",triggerDescription=").append(mTriggerDescription)
.append(",type=").append(mType);
@@ -498,6 +521,7 @@
&& other.creationTime == creationTime;
if (Flags.modesApi()) {
return finalEquals
+ && Objects.equals(other.mDeviceEffects, mDeviceEffects)
&& other.mAllowManualInvocation == mAllowManualInvocation
&& other.mIconResId == mIconResId
&& Objects.equals(other.mTriggerDescription, mTriggerDescription)
@@ -510,8 +534,8 @@
public int hashCode() {
if (Flags.modesApi()) {
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
- configurationActivity, mZenPolicy, mModified, creationTime, mPkg,
- mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
+ configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
+ mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
}
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -573,6 +597,7 @@
private boolean mEnabled;
private ComponentName mConfigurationActivity = null;
private ZenPolicy mPolicy = null;
+ private ZenDeviceEffects mDeviceEffects = null;
private int mType;
private String mDescription;
private int mIconResId;
@@ -588,6 +613,7 @@
mEnabled = rule.isEnabled();
mConfigurationActivity = rule.getConfigurationActivity();
mPolicy = rule.getZenPolicy();
+ mDeviceEffects = rule.getDeviceEffects();
mType = rule.getType();
mDescription = rule.getTriggerDescription();
mIconResId = rule.getIconResId();
@@ -639,6 +665,17 @@
}
/**
+ * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes
+ * to the device behavior that should apply while the rule is active, but are not directly
+ * related to suppressing notifications (for example: disabling always-on display).
+ */
+ @NonNull
+ public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ /**
* Sets the type of the rule
*/
public @NonNull Builder setType(@Type int type) {
@@ -687,6 +724,7 @@
public @NonNull AutomaticZenRule build() {
AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
mConditionId, mPolicy, mInterruptionFilter, mEnabled);
+ rule.mDeviceEffects = mDeviceEffects;
rule.creationTime = mCreationTime;
rule.mType = mType;
rule.mTriggerDescription = mDescription;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ec5effd..9438571 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -214,6 +214,8 @@
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
AutomaticZenRule getAutomaticZenRule(String id);
+ Map<String, AutomaticZenRule> getAutomaticZenRules();
+ // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
List<ZenModeConfig.ZenRule> getZenRules();
String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg);
boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 545ba8e..6aad168 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -64,6 +64,8 @@
import com.android.internal.widget.PasswordValidationError;
import com.android.internal.widget.VerifyCredentialResponse;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
@@ -235,6 +237,7 @@
PIN,
PATTERN
})
+ @Retention(RetentionPolicy.SOURCE)
@interface LockTypes {}
private final IKeyguardLockedStateListener mIKeyguardLockedStateListener =
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2d80b1f..337e3f1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -924,6 +924,7 @@
VISIBILITY_SECRET,
NotificationManager.VISIBILITY_NO_OVERRIDE
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface NotificationVisibilityOverride{};
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index ffb79b3..51c937d 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1256,17 +1256,21 @@
public Map<String, AutomaticZenRule> getAutomaticZenRules() {
INotificationManager service = getService();
try {
- List<ZenModeConfig.ZenRule> rules = service.getZenRules();
- Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
- for (ZenModeConfig.ZenRule rule : rules) {
- AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component,
- rule.configurationActivity, rule.conditionId, rule.zenPolicy,
- zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
- rule.creationTime);
- azr.setPackageName(rule.pkg);
- ruleMap.put(rule.id, azr);
+ if (Flags.modesApi()) {
+ return service.getAutomaticZenRules();
+ } else {
+ List<ZenModeConfig.ZenRule> rules = service.getZenRules();
+ Map<String, AutomaticZenRule> ruleMap = new HashMap<>();
+ for (ZenModeConfig.ZenRule rule : rules) {
+ AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component,
+ rule.configurationActivity, rule.conditionId, rule.zenPolicy,
+ zenModeToInterruptionFilter(rule.zenMode), rule.enabled,
+ rule.creationTime);
+ azr.setPackageName(rule.pkg);
+ ruleMap.put(rule.id, azr);
+ }
+ return ruleMap;
}
- return ruleMap;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fa52968..79a5879 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -113,6 +113,7 @@
import android.hardware.lights.LightsManager;
import android.hardware.lights.SystemLightsManager;
import android.hardware.location.ContextHubManager;
+import android.hardware.location.IContextHubService;
import android.hardware.radio.RadioManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
@@ -1118,8 +1119,12 @@
new CachedServiceFetcher<ContextHubManager>() {
@Override
public ContextHubManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- return new ContextHubManager(ctx.getOuterContext(),
- ctx.mMainThread.getHandler().getLooper());
+ IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE);
+ if (b == null) {
+ return null;
+ }
+ return new ContextHubManager(IContextHubService.Stub.asInterface(b),
+ ctx.mMainThread.getHandler().getLooper());
}});
registerService(Context.INCIDENT_SERVICE, IncidentManager.class,
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 019a1a8..4621634 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -42,6 +42,8 @@
import android.view.WindowManager;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -120,6 +122,7 @@
WINDOWING_MODE_PINNED,
WINDOWING_MODE_FREEFORM,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface WindowingMode {}
/** The current activity type of the configuration. */
@@ -147,6 +150,7 @@
ACTIVITY_TYPE_ASSISTANT,
ACTIVITY_TYPE_DREAM,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ActivityType {}
/** The current always on top status of the configuration. */
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index e4ee959..14462b8 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -47,6 +47,8 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
@@ -175,6 +177,7 @@
public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+ @Retention(RetentionPolicy.SOURCE)
private @interface HeadlessDeviceOwnerMode {}
/** @hide */
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index 84b1ca5..b0bec78 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -164,11 +164,8 @@
/**
* String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
- *
- * @hide
*/
@FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
- @TestApi
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3df11f6..3ee9d692 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3998,8 +3998,7 @@
/**
* An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which
- * resource IDs (see {@link DevicePolicyResources.Drawables} and
- * {@link DevicePolicyResources.Strings}) have been updated.
+ * resource IDs (i.e. strings and drawables) have been updated.
*/
public static final String EXTRA_RESOURCE_IDS =
"android.app.extra.RESOURCE_IDS";
@@ -8370,9 +8369,7 @@
* Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
* successfully set or not. This callback will contain:
* <ul>
- * <li> The policy identifier returned from
- * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction
- * {@link UserManager#DISALLOW_CAMERA}
+ * <li> The policy identifier: userRestriction_no_camera
* <li> The {@link TargetUser} that this policy relates to
* <li> The {@link PolicyUpdateResult}, which will be
* {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
@@ -13403,7 +13400,7 @@
* A device owner, by default, may continue granting these permissions. However, for increased
* user control, the admin may opt out of controlling grants for these permissions by including
* {@link #EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters.
- * In that case the device owner's control will be limited do denying these permissions.
+ * In that case the device owner's control will be limited to denying these permissions.
* <p>
* NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over
* the following permissions are restricted for managed profile owners:
@@ -17113,19 +17110,19 @@
* Returns {@code true} if this device is marked as a financed device.
*
* <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages})
- * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
+ * by the holder of the role {@code android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}.
* If this occurs, Device Owners and Profile Owners that have set lock task packages or
* features, or that attempt to set lock task packages or features, will receive a callback
* indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and
* {@link PolicyUpdateReceiver#onPolicySetResult}.
*
* <p>To be informed of changes to this status you can subscribe to the broadcast
- * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}.
+ * {@link #ACTION_DEVICE_FINANCING_STATE_CHANGED}.
*
* @throws SecurityException if the caller is not a device owner, profile owner of an
* organization-owned managed profile, profile owner on the primary user or holder of one of the
- * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT},
- * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION.
+ * following roles: {@code android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT},
+ * {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}.
*/
public boolean isDeviceFinanced() {
throwIfParentInstance("isDeviceFinanced");
diff --git a/core/java/android/app/admin/DevicePolicyResourcesManager.java b/core/java/android/app/admin/DevicePolicyResourcesManager.java
index 2cc189f..7a71231 100644
--- a/core/java/android/app/admin/DevicePolicyResourcesManager.java
+++ b/core/java/android/app/admin/DevicePolicyResourcesManager.java
@@ -452,7 +452,7 @@
/**
* Returns the appropriate updated string for the {@code stringId} (see
- * {@link DevicePolicyResources.Strings}) if one was set using
+ * {@code DevicePolicyResources.Strings}) if one was set using
* {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}.
*
* <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index a6595fe..b5c66ff 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -86,7 +86,9 @@
EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
EVENT_VENDOR_WEARABLE_START,
- }) public @interface EventCode {}
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventCode {}
/** The integer indicating an unknown level. */
public static final int LEVEL_UNKNOWN = 0;
@@ -114,7 +116,9 @@
LEVEL_MEDIUM,
LEVEL_MEDIUM_HIGH,
LEVEL_HIGH
- }) public @interface LevelValue {}
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LevelValue {}
@EventCode private final int mEventType;
private static int defaultEventType() {
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index bf383f1..159481f 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -32,6 +32,8 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -106,7 +108,9 @@
STATUS_SERVICE_UNAVAILABLE,
STATUS_MICROPHONE_DISABLED,
STATUS_ACCESS_DENIED
- }) public @interface StatusCode {}
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusCode {}
/**
* Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent.
diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java
index c86142e..dab1657 100644
--- a/core/java/android/app/cloudsearch/SearchResponse.java
+++ b/core/java/android/app/cloudsearch/SearchResponse.java
@@ -21,6 +21,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -37,6 +39,7 @@
SEARCH_STATUS_OK,
SEARCH_STATUS_TIME_OUT,
SEARCH_STATUS_NO_INTERNET})
+ @Retention(RetentionPolicy.SOURCE)
public @interface SearchStatusCode {
}
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index dd332c8..bc8fac5 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -71,17 +71,13 @@
@NonNull
public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken,
@NonNull Configuration config) {
- if (config == null) {
- throw new IllegalArgumentException("Config must not be null.");
- }
-
ActivityConfigurationChangeItem instance =
ObjectPool.obtain(ActivityConfigurationChangeItem.class);
if (instance == null) {
instance = new ActivityConfigurationChangeItem();
}
instance.setActivityToken(activityToken);
- instance.mConfiguration = config;
+ instance.mConfiguration = new Configuration(config);
return instance;
}
@@ -89,7 +85,7 @@
@Override
public void recycle() {
super.recycle();
- mConfiguration = Configuration.EMPTY;
+ mConfiguration = null;
ObjectPool.recycle(this);
}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index a5dd115..3ce094e 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -32,6 +32,7 @@
import com.android.internal.content.ReferrerIntent;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -51,7 +52,7 @@
/**
* A record that was properly configured for relaunch. Execution will be cancelled if not
- * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}.
+ * initialized after {@link #preExecute(ClientTransactionHandler)}.
*/
private ActivityClientRecord mActivityClientRecord;
@@ -99,10 +100,11 @@
instance = new ActivityRelaunchItem();
}
instance.setActivityToken(activityToken);
- instance.mPendingResults = pendingResults;
- instance.mPendingNewIntents = pendingNewIntents;
+ instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null;
+ instance.mPendingNewIntents =
+ pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null;
instance.mConfigChanges = configChanges;
- instance.mConfig = config;
+ instance.mConfig = new MergedConfiguration(config);
instance.mPreserveWindow = preserveWindow;
return instance;
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 24fced4..51a09fb 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -35,6 +35,7 @@
import android.os.Parcelable;
import android.os.Trace;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -82,7 +83,7 @@
instance = new ActivityResultItem();
}
instance.setActivityToken(activityToken);
- instance.mResultInfoList = resultInfoList;
+ instance.mResultInfoList = new ArrayList<>(resultInfoList);
return instance;
}
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index 2a65b35..b4ff476f 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -20,6 +20,8 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -93,7 +95,7 @@
}
void setActivityToken(@NonNull IBinder activityToken) {
- mActivityToken = activityToken;
+ mActivityToken = requireNonNull(activityToken);
}
// To be overridden
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 9c0cd39..7c34cde 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -54,12 +54,14 @@
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
+ @Nullable
private List<ClientTransactionItem> mActivityCallbacks;
/**
* Final lifecycle state in which the client activity should be after the transaction is
* executed.
*/
+ @Nullable
private ActivityLifecycleItem mLifecycleStateRequest;
/** Target client. */
@@ -123,6 +125,7 @@
@VisibleForTesting(visibility = PACKAGE)
@UnsupportedAppUsage
@Deprecated
+ @Nullable
public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
@@ -207,7 +210,7 @@
for (int i = 0; i < size; i++) {
mActivityCallbacks.get(i).recycle();
}
- mActivityCallbacks.clear();
+ mActivityCallbacks = null;
}
if (mLifecycleStateRequest != null) {
mLifecycleStateRequest.recycle();
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 96961ace..0e327a7 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -64,7 +64,7 @@
if (instance == null) {
instance = new ConfigurationChangeItem();
}
- instance.mConfiguration = config;
+ instance.mConfiguration = new Configuration(config);
instance.mDeviceId = deviceId;
return instance;
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index a64c744..d2ef65a 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -45,6 +45,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -135,10 +136,16 @@
if (instance == null) {
instance = new LaunchActivityItem();
}
- setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId,
- referrer, voiceInteractor, procState, state, persistentState, pendingResults,
- pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
- activityClientController, shareableActivityToken,
+ setValues(instance, activityToken, new Intent(intent), ident, new ActivityInfo(info),
+ new Configuration(curConfig), new Configuration(overrideConfig), deviceId,
+ referrer, voiceInteractor, procState,
+ state != null ? new Bundle(state) : null,
+ persistentState != null ? new PersistableBundle(persistentState) : null,
+ pendingResults != null ? new ArrayList<>(pendingResults) : null,
+ pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
+ activityOptions, isForward,
+ profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
+ assistToken, activityClientController, shareableActivityToken,
launchedFromBubble, taskFragmentToken);
return instance;
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index e56d3f8..961da19 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -69,7 +69,7 @@
}
instance.setActivityToken(activityToken);
instance.mTargetDisplayId = targetDisplayId;
- instance.mConfiguration = configuration;
+ instance.mConfiguration = new Configuration(configuration);
return instance;
}
@@ -78,7 +78,7 @@
public void recycle() {
super.recycle();
mTargetDisplayId = 0;
- mConfiguration = Configuration.EMPTY;
+ mConfiguration = null;
ObjectPool.recycle(this);
}
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 8e995aa..acf2ea4 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -32,6 +32,7 @@
import com.android.internal.content.ReferrerIntent;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -71,7 +72,7 @@
instance = new NewIntentItem();
}
instance.setActivityToken(activityToken);
- instance.mIntents = intents;
+ instance.mIntents = new ArrayList<>(intents);
instance.mResume = resume;
return instance;
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index 375d1bf..cbad92f 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -65,7 +65,7 @@
instance = new WindowContextInfoChangeItem();
}
instance.mClientToken = requireNonNull(clientToken);
- instance.mInfo = new WindowContextInfo(config, displayId);
+ instance.mInfo = new WindowContextInfo(new Configuration(config), displayId);
return instance;
}
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 9828133..7d3eb87 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -77,10 +77,10 @@
instance = new WindowStateResizeItem();
}
instance.mWindow = requireNonNull(window);
- instance.mFrames = requireNonNull(frames);
+ instance.mFrames = new ClientWindowFrames(frames);
instance.mReportDraw = reportDraw;
- instance.mConfiguration = requireNonNull(configuration);
- instance.mInsetsState = requireNonNull(insetsState);
+ instance.mConfiguration = new MergedConfiguration(configuration);
+ instance.mInsetsState = new InsetsState(insetsState);
instance.mForceLayout = forceLayout;
instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
instance.mDisplayId = displayId;
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index d0b4fe4..f1ca086 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -35,6 +35,8 @@
import android.service.wearable.WearableSensingService;
import android.system.OsConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -105,7 +107,9 @@
STATUS_SERVICE_UNAVAILABLE,
STATUS_WEARABLE_UNAVAILABLE,
STATUS_ACCESS_DENIED
- }) public @interface StatusCode {}
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StatusCode {}
private final Context mContext;
private final IWearableSensingManager mService;
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
new file mode 100644
index 0000000..6a735a4
--- /dev/null
+++ b/core/java/android/appwidget/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.appwidget.flags"
+
+flag {
+ name: "generated_previews"
+ namespace: "app_widgets"
+ description: "Enable support for generated previews in AppWidgetManager"
+ bug: "306546610"
+}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 2f97080..102cbf3 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,8 +23,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
+import android.companion.virtual.camera.VirtualCameraConfig;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.Point;
@@ -236,8 +235,15 @@
void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
/**
- * Creates a new VirtualCamera and registers it with the VirtualCameraProvider.
+ * Creates a new virtual camera and registers it with the virtual camera service.
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
- void registerVirtualCamera(in IVirtualCamera camera);
+ void registerVirtualCamera(in VirtualCameraConfig camera);
+
+ /**
+ * Destroys the virtual camera with given config and unregisters it from the virtual camera
+ * service.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void unregisterVirtualCamera(in VirtualCameraConfig camera);
}
diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java
index 93a3e78..d0c8be6 100644
--- a/core/java/android/companion/virtual/VirtualDevice.java
+++ b/core/java/android/companion/virtual/VirtualDevice.java
@@ -32,9 +32,8 @@
* Details of a particular virtual device.
*
* <p>Read-only device representation exposing the properties of an existing virtual device.
- *
- * @see VirtualDeviceManager#registerVirtualDeviceListener
*/
+// TODO(b/310912420): Link to VirtualDeviceManager#registerVirtualDeviceListener from the docs
public final class VirtualDevice implements Parcelable {
private final @NonNull IVirtualDevice mVirtualDevice;
@@ -92,8 +91,8 @@
* per device.
*
* @see Context#createDeviceContext
- * @see #getPersistentDeviceId
*/
+ // TODO(b/310912420): Link to #getPersistentDeviceId from the docs
public int getDeviceId() {
return mId;
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 60965a8..41c90b9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -212,9 +212,10 @@
* existing virtual devices.</p>
*
* <p>Note that if a virtual device is closed and becomes invalid, the returned objects will
- * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real
- * time updates of the availability of virtual devices.</p>
+ * not be updated and may contain stale values.</p>
*/
+ // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the
+ // availability of virtual devices." in the note paragraph above with a link annotation.
@NonNull
public List<android.companion.virtual.VirtualDevice> getVirtualDevices() {
if (mService == null) {
diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
deleted file mode 100644
index 58b850d..0000000
--- a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl
+++ /dev/null
@@ -1,17 +0,0 @@
-package android.companion.virtual.camera;
-
-import android.companion.virtual.camera.IVirtualCameraSession;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-
-/**
- * Counterpart of ICameraDevice for virtual camera.
- *
- * @hide
- */
-interface IVirtualCamera {
-
- IVirtualCameraSession open();
-
- VirtualCameraHalConfig getHalConfig();
-
-}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
new file mode 100644
index 0000000..fac44b5
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtual.camera.VirtualCameraMetadata;
+import android.view.Surface;
+
+/**
+ * Interface for the virtual camera service and system server to talk back to the virtual camera owner.
+ *
+ * @hide
+ */
+interface IVirtualCameraCallback {
+
+ /**
+ * Called when one of the requested stream has been configured by the virtual camera service and
+ * is ready to receive data onto its {@link Surface}
+ *
+ * @param streamId The id of the configured stream
+ * @param surface The surface to write data into for this stream
+ * @param streamConfig The image data configuration for this stream
+ */
+ oneway void onStreamConfigured(
+ int streamId,
+ in Surface surface,
+ in VirtualCameraStreamConfig streamConfig);
+
+ /**
+ * The client application is requesting a camera frame for the given streamId with the provided
+ * metadata.
+ *
+ * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
+ * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)} call.
+ *
+ * @param streamId The streamId for which the frame is requested. This corresponds to the
+ * streamId that was given in {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)}
+ * @param frameId The frameId that is being requested. Each request will have a different
+ * frameId, that will be increasing for each call with a particular streamId.
+ * @param metadata The metadata requested for the frame. The virtual camera should do its best
+ * to honor the requested metadata.
+ */
+ oneway void onProcessCaptureRequest(
+ int streamId, long frameId, in VirtualCameraMetadata metadata);
+
+ /**
+ * The stream previously configured when {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
+ * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner
+ *
+ * @param streamId The id of the stream that was closed.
+ */
+ oneway void onStreamClosed(int streamId);
+
+}
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java
index 791bf0a..beee86f 100644
--- a/core/java/android/companion/virtual/camera/VirtualCamera.java
+++ b/core/java/android/companion/virtual/camera/VirtualCamera.java
@@ -16,20 +16,44 @@
package android.companion.virtual.camera;
+import android.annotation.FlaggedApi;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.flags.Flags;
+import android.hardware.camera2.CameraDevice;
import android.os.RemoteException;
import androidx.annotation.NonNull;
+import java.io.Closeable;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * Virtual camera that is used to send image data into system.
+ * A VirtualCamera is the representation of a remote or computer generated camera that will be
+ * exposed to applications using the Android Camera APIs.
*
+ * <p>A VirtualCamera is created using {@link
+ * VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)}.
+ *
+ * <p>Once a virtual camera is created, it will receive callbacks from the system when an
+ * application attempts to use it via the {@link VirtualCameraCallback} class set using {@link
+ * VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)}
+ *
+ * @see VirtualDeviceManager.VirtualDevice#createVirtualDevice(int, VirtualDeviceParams)
+ * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)
+ * @see android.hardware.camera2.CameraManager#openCamera(String, CameraDevice.StateCallback,
+ * android.os.Handler)
* @hide
*/
-public final class VirtualCamera extends IVirtualCamera.Stub {
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCamera implements Closeable {
+ private final IVirtualDevice mVirtualDevice;
private final VirtualCameraConfig mConfig;
/**
@@ -37,40 +61,35 @@
*
* @param virtualDevice The Binder object representing this camera in the server.
* @param config Configuration for the new virtual camera
+ * @hide
*/
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public VirtualCamera(
@NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) {
+ mVirtualDevice = virtualDevice;
mConfig = Objects.requireNonNull(config);
Objects.requireNonNull(virtualDevice);
+ // TODO(b/310857519): Avoid registration inside constructor.
try {
- virtualDevice.registerVirtualCamera(this);
+ mVirtualDevice.registerVirtualCamera(config);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
- /** Get the camera session associated with this device */
- @Override
- public IVirtualCameraSession open() {
- // TODO: b/302255544 - Make this async.
- VirtualCameraSession session = mConfig.getCallback().onOpenSession();
- return new VirtualCameraSessionInternal(session);
- }
-
/** Returns the configuration of this virtual camera instance. */
@NonNull
public VirtualCameraConfig getConfig() {
return mConfig;
}
- /**
- * Returns the configuration to be used by the virtual camera HAL.
- *
- * @hide
- */
@Override
- @NonNull
- public VirtualCameraHalConfig getHalConfig() {
- return mConfig.getHalConfig();
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void close() {
+ try {
+ mVirtualDevice.unregisterVirtualCamera(mConfig);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
index a7c3d4f..a18ae03 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java
@@ -16,7 +16,12 @@
package android.companion.virtual.camera;
-import android.hardware.camera2.params.SessionConfiguration;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.view.Surface;
import java.util.concurrent.Executor;
@@ -24,15 +29,53 @@
* Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks
* from the framework and the camera system.
*
- * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback)
+ * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public interface VirtualCameraCallback {
/**
- * Called when a client opens a new camera session for the associated {@link VirtualCamera}
+ * Called when one of the requested stream has been configured by the virtual camera service and
+ * is ready to receive data onto its {@link Surface}
*
- * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration)
+ * @param streamId The id of the configured stream
+ * @param surface The surface to write data into for this stream
+ * @param streamConfig The image data configuration for this stream
*/
- VirtualCameraSession onOpenSession();
+ void onStreamConfigured(
+ int streamId,
+ @NonNull Surface surface,
+ @NonNull VirtualCameraStreamConfig streamConfig);
+
+ /**
+ * The client application is requesting a camera frame for the given streamId with the provided
+ * metadata.
+ *
+ * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to
+ * this stream that was provided during the {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)} call.
+ *
+ * @param streamId The streamId for which the frame is requested. This corresponds to the
+ * streamId that was given in {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)}
+ * @param frameId The frameId that is being requested. Each request will have a different
+ * frameId, that will be increasing for each call with a particular streamId.
+ * @param metadata The metadata requested for the frame. The virtual camera should do its best
+ * to honor the requested metadata but the consumer won't be informed about the metadata set
+ * for a particular frame. If null, the requested frame can be anything the producer sends.
+ */
+ void onProcessCaptureRequest(
+ int streamId, long frameId, @Nullable VirtualCameraMetadata metadata);
+
+ /**
+ * The stream previously configured when {@link #onStreamConfigured(int, Surface,
+ * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be
+ * freed. The Surface corresponding to that streamId was disposed on the client side and should
+ * not be used anymore by the virtual camera owner
+ *
+ * @param streamId The id of the stream that was closed.
+ */
+ void onStreamClosed(int streamId);
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
similarity index 76%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
index 2529807..88c27a5 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl
@@ -15,14 +15,5 @@
*/
package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
-
- void configureStream(int width, int height, int format);
-
- void close();
-}
+/** @hide */
+parcelable VirtualCameraConfig;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index fb464d5..f1eb240 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -18,11 +18,19 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.content.res.Resources;
import android.graphics.ImageFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArraySet;
+import android.view.Surface;
-import java.util.Collections;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -33,33 +41,115 @@
*
* @hide
*/
-public final class VirtualCameraConfig {
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraConfig implements Parcelable {
- private final String mDisplayName;
+ private final @StringRes int mNameStringRes;
private final Set<VirtualCameraStreamConfig> mStreamConfigurations;
- private final VirtualCameraCallback mCallback;
- private final Executor mCallbackExecutor;
+ private final IVirtualCameraCallback mCallback;
+
+ private VirtualCameraConfig(
+ int displayNameStringRes,
+ @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
+ @NonNull Executor executor,
+ @NonNull VirtualCameraCallback callback) {
+ mNameStringRes = displayNameStringRes;
+ mStreamConfigurations =
+ Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations"));
+ if (mStreamConfigurations.isEmpty()) {
+ throw new IllegalArgumentException(
+ "At least one stream configuration is needed to create a virtual camera.");
+ }
+ mCallback =
+ new VirtualCameraCallbackInternal(
+ requireNonNull(callback, "Missing callback"),
+ requireNonNull(executor, "Missing callback executor"));
+ }
+
+ private VirtualCameraConfig(@NonNull Parcel in) {
+ mNameStringRes = in.readInt();
+ mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder());
+ mStreamConfigurations =
+ Set.of(
+ in.readParcelableArray(
+ VirtualCameraStreamConfig.class.getClassLoader(),
+ VirtualCameraStreamConfig.class));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mNameStringRes);
+ dest.writeStrongInterface(mCallback);
+ dest.writeParcelableArray(
+ mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags);
+ }
+
+ /**
+ * @return The display name of this VirtualCamera
+ */
+ @StringRes
+ public int getDisplayNameStringRes() {
+ return mNameStringRes;
+ }
+
+ /**
+ * Returns an unmodifiable set of the stream configurations added to this {@link
+ * VirtualCameraConfig}.
+ *
+ * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int)
+ */
+ @NonNull
+ public Set<VirtualCameraStreamConfig> getStreamConfigs() {
+ return mStreamConfigurations;
+ }
+
+ /**
+ * Returns the callback used to communicate from the server to the client.
+ *
+ * @hide
+ */
+ @NonNull
+ public IVirtualCameraCallback getCallback() {
+ return mCallback;
+ }
/**
* Builder for {@link VirtualCameraConfig}.
*
* <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met:
- * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}.
- * <li>A name must be set with {@link #setDisplayName(String)}
- * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)}
+ * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}.
+ * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
+ * VirtualCameraCallback)}
+ * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)}
*/
+ @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private String mDisplayName;
- private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>();
+ private @StringRes int mDisplayNameStringRes = Resources.ID_NULL;
+ private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
- /** Set the visible name of this camera for the user. */
- // TODO: b/290172356 - Take a resource id instead of displayName
+ /**
+ * Set the visible name of this camera for the user.
+ *
+ * <p>Sets the resource to a string representing a user readable name for this virtual
+ * camera.
+ *
+ * @throws IllegalArgumentException if an invalid resource id is passed.
+ */
@NonNull
- public Builder setDisplayName(@NonNull String displayName) {
- mDisplayName = requireNonNull(displayName);
+ public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) {
+ if (displayNameStringRes <= 0) {
+ throw new IllegalArgumentException("Invalid resource passed for display name");
+ }
+ mDisplayNameStringRes = displayNameStringRes;
return this;
}
@@ -68,18 +158,21 @@
*
* <p>At least one {@link VirtualCameraStreamConfig} must be added.
*
- * @param width The width of the stream
- * @param height The height of the stream
- * @param format The {@link ImageFormat} of the stream
+ * @param width The width of the stream.
+ * @param height The height of the stream.
+ * @param format The {@link ImageFormat} of the stream.
+ *
+ * @throws IllegalArgumentException if invalid format or dimensions are passed.
*/
@NonNull
- public Builder addStreamConfiguration(
- int width, int height, @ImageFormat.Format int format) {
- VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig();
- streamConfig.width = width;
- streamConfig.height = height;
- streamConfig.format = format;
- mStreamConfiguration.add(streamConfig);
+ public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Invalid dimensions passed for stream config");
+ }
+ if (!ImageFormat.isPublicFormat(format)) {
+ throw new IllegalArgumentException("Invalid format passed for stream config");
+ }
+ mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format));
return this;
}
@@ -93,7 +186,9 @@
* @param callback The instance of the callback to be added. Subsequent call to this method
* will replace the callback set.
*/
- public Builder setCallback(
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder") // The configuration is immutable
+ public Builder setVirtualCameraCallback(
@NonNull Executor executor, @NonNull VirtualCameraCallback callback) {
mCallbackExecutor = requireNonNull(executor);
mCallback = requireNonNull(callback);
@@ -108,67 +203,49 @@
@NonNull
public VirtualCameraConfig build() {
return new VirtualCameraConfig(
- mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback);
+ mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback);
}
}
- private VirtualCameraConfig(
- @NonNull String displayName,
- @NonNull Set<VirtualCameraStreamConfig> streamConfigurations,
- @NonNull Executor executor,
- @NonNull VirtualCameraCallback callback) {
- mDisplayName = requireNonNull(displayName, "Missing display name");
- mStreamConfigurations =
- Collections.unmodifiableSet(
- requireNonNull(streamConfigurations, "Missing stream configuration"));
- if (mStreamConfigurations.isEmpty()) {
- throw new IllegalArgumentException(
- "At least one StreamConfiguration is needed to create a virtual camera.");
+ private static class VirtualCameraCallbackInternal extends IVirtualCameraCallback.Stub {
+
+ private final VirtualCameraCallback mCallback;
+ private final Executor mExecutor;
+
+ private VirtualCameraCallbackInternal(VirtualCameraCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
}
- mCallback = requireNonNull(callback, "Missing callback");
- mCallbackExecutor = requireNonNull(executor, "Missing callback executor");
+
+ @Override
+ public void onStreamConfigured(
+ int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) {
+ mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig));
+ }
+
+ @Override
+ public void onProcessCaptureRequest(
+ int streamId, long frameId, VirtualCameraMetadata metadata) {
+ mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata));
+ }
+
+ @Override
+ public void onStreamClosed(int streamId) {
+ mExecutor.execute(() -> mCallback.onStreamClosed(streamId));
+ }
}
- /**
- * @return The display name of this VirtualCamera
- */
@NonNull
- public String getDisplayName() {
- return mDisplayName;
- }
+ public static final Parcelable.Creator<VirtualCameraConfig> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public VirtualCameraConfig createFromParcel(Parcel in) {
+ return new VirtualCameraConfig(in);
+ }
- /**
- * Returns an unmodifiable set of the stream configurations added to this {@link
- * VirtualCameraConfig}.
- *
- * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int)
- */
- @NonNull
- public Set<VirtualCameraStreamConfig> getStreamConfigs() {
- return mStreamConfigurations;
- }
-
- /** Returns the callback used to communicate from the server to the client. */
- @NonNull
- public VirtualCameraCallback getCallback() {
- return mCallback;
- }
-
- /** Returns the executor onto which the callback should be run. */
- @NonNull
- public Executor getCallbackExecutor() {
- return mCallbackExecutor;
- }
-
- /**
- * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this
- * {@link VirtualCameraConfig}
- */
- @NonNull
- public VirtualCameraHalConfig getHalConfig() {
- VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig();
- halConfig.displayName = mDisplayName;
- halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]);
- return halConfig;
- }
+ @Override
+ public VirtualCameraConfig[] newArray(int size) {
+ return new VirtualCameraConfig[size];
+ }
+ };
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
deleted file mode 100644
index 7070a38..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl
+++ /dev/null
@@ -1,12 +0,0 @@
-package android.companion.virtual.camera;
-
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
-
-/**
- * Configuration for VirtualCamera to be passed to the server and HAL service.
- * @hide
- */
-parcelable VirtualCameraHalConfig {
- String displayName;
- VirtualCameraStreamConfig[] streamConfigs;
-}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
similarity index 79%
rename from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
rename to core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
index 2529807..6c1f0fc 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl
@@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.companion.virtual.camera;
/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
+ * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with
+ * VirtualCamera.
* @hide
*/
-interface IVirtualCameraSession {
-
- void configureStream(int width, int height, int format);
-
- void close();
-}
+parcelable VirtualCameraMetadata;
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
new file mode 100644
index 0000000..1ba36d0
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.camera;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data structure used to store camera metadata compatible with VirtualCamera.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraMetadata implements Parcelable {
+
+ /** @hide */
+ public VirtualCameraMetadata(@NonNull Parcel in) {}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {}
+
+ @NonNull
+ public static final Creator<VirtualCameraMetadata> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public VirtualCameraMetadata createFromParcel(Parcel in) {
+ return new VirtualCameraMetadata(in);
+ }
+
+ @Override
+ @NonNull
+ public VirtualCameraMetadata[] newArray(int size) {
+ return new VirtualCameraMetadata[size];
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraSession.java
deleted file mode 100644
index c25d977..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraSession.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-/***
- * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing
- * images from a {@link VirtualCamera}.
- * @hide
- */
-// TODO: b/289881985 - This is just a POC implementation for now, this will be extended
-// to a full featured Camera Session
-public interface VirtualCameraSession {
-
- /** Close the session and release its resources. */
- default void close() {}
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
deleted file mode 100644
index da168de..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.graphics.ImageFormat;
-
-import androidx.annotation.NonNull;
-
-import java.util.Objects;
-
-/**
- * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}.
- *
- * @hide
- */
-final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub {
-
- @SuppressWarnings("FieldCanBeLocal")
- // TODO: b/289881985: Will be used once connected with the CameraService
- private final VirtualCameraSession mVirtualCameraSession;
-
- VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) {
- mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession);
- }
-
- @Override
- public void configureStream(int width, int height, @ImageFormat.Format int format) {}
-
- public void close() {}
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
index 304d455..ce92b6d 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl
@@ -16,11 +16,7 @@
package android.companion.virtual.camera;
/**
- * A stream configuration supported by a virtual camera
+ * The configuration of a single virtual camera stream.
* @hide
*/
-parcelable VirtualCameraStreamConfig {
- int width;
- int height;
- int format;
-}
+parcelable VirtualCameraStreamConfig;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
new file mode 100644
index 0000000..e198821
--- /dev/null
+++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.companion.virtual.camera;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
+import android.graphics.ImageFormat;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The configuration of a single virtual camera stream.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
+public final class VirtualCameraStreamConfig implements Parcelable {
+
+ private final int mWidth;
+ private final int mHeight;
+ private final int mFormat;
+
+ /**
+ * Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided
+ * width, height and {@link ImageFormat}
+ *
+ * @param width The width of the stream.
+ * @param height The height of the stream.
+ * @param format The {@link ImageFormat} of the stream.
+ */
+ public VirtualCameraStreamConfig(
+ @IntRange(from = 1) int width,
+ @IntRange(from = 1) int height,
+ @ImageFormat.Format int format) {
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mFormat = format;
+ }
+
+ private VirtualCameraStreamConfig(@NonNull Parcel in) {
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mFormat = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ dest.writeInt(mFormat);
+ }
+
+ @NonNull
+ public static final Creator<VirtualCameraStreamConfig> CREATOR =
+ new Creator<>() {
+ @Override
+ public VirtualCameraStreamConfig createFromParcel(Parcel in) {
+ return new VirtualCameraStreamConfig(in);
+ }
+
+ @Override
+ public VirtualCameraStreamConfig[] newArray(int size) {
+ return new VirtualCameraStreamConfig[size];
+ }
+ };
+
+ /** Returns the width of this stream. */
+ @IntRange(from = 1)
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /** Returns the height of this stream. */
+ @IntRange(from = 1)
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /** Returns the {@link ImageFormat} of this stream. */
+ @ImageFormat.Format
+ public int getFormat() {
+ return mFormat;
+ }
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index ea54c91..6b39f26 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2800,6 +2800,12 @@
/**
* Broadcast Action: An application package that was previously in the stopped state has been
* started and is no longer considered stopped.
+ * <p>When a package is force-stopped, the {@link #ACTION_PACKAGE_RESTARTED} broadcast is sent
+ * and the package in the stopped state cannot self-start for any reason unless there's an
+ * explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED}
+ * broadcast is sent when such an explicit process start occurs and the package is taken
+ * out of the stopped state.
+ * </p>
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
@@ -2807,6 +2813,9 @@
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
+ *
+ * @see ApplicationInfo#FLAG_STOPPED
+ * @see #ACTION_PACKAGE_RESTARTED
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED)
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index cc12949..a4d5327 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -22,18 +22,11 @@
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
-import android.graphics.Paint;
import android.graphics.drawable.Drawable;
-import android.icu.text.UnicodeSet;
import android.os.UserHandle;
import android.os.UserManager;
-import android.text.TextUtils;
import android.util.DisplayMetrics;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Objects;
-
/**
* A representation of an activity that can belong to this user or a managed
* profile associated with this user. It can be used to query the label, icon
@@ -43,10 +36,6 @@
private final PackageManager mPm;
private final LauncherActivityInfoInternal mInternal;
- private static final UnicodeSet TRIMMABLE_CHARACTERS =
- new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]",
- /* ignoreWhitespace= */ false).freeze();
-
/**
* Create a launchable activity object for a given ResolveInfo and user.
*
@@ -83,28 +72,13 @@
}
/**
- * Retrieves the label for the activity. The returned label can be different
- * from {@link ActivityInfo#loadLabel(PackageManager)} or
- * {@link PackageItemInfo#loadLabel(PackageManager)}. The returned result is trimmed.
- * If the activity's label is empty, use the application's label instead.
- * If the application's label is still empty, use the package name instead.
+ * Retrieves the label for the activity.
*
- * @return The label for the activity. If the activity's label is empty,
- * return the application's label instead. If the application's label
- * is still empty, return the package name instead.
+ * @return The label for the activity.
*/
public CharSequence getLabel() {
- CharSequence label = trim(getActivityInfo().loadLabel(mPm));
- // If the trimmed label is empty, use application's label instead
- if (TextUtils.isEmpty(label)) {
- label = trim(getApplicationInfo().loadLabel(mPm));
- // If the trimmed label is still empty, use package name instead
- if (TextUtils.isEmpty(label)) {
- label = getComponentName().getPackageName();
- }
- }
// TODO: Go through LauncherAppsService
- return label;
+ return getActivityInfo().loadLabel(mPm);
}
/**
@@ -206,149 +180,4 @@
return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
}
-
- /**
- * If the {@code ch} is trimmable, return {@code true}. Otherwise, return
- * {@code false}. If the count of the code points of {@code ch} doesn't
- * equal 1, return {@code false}.
- * <p>
- * There are two types of the trimmable characters.
- * 1. The character is one of the Default_Ignorable_Code_Point in
- * <a href="
- * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt">
- * DerivedCoreProperties.txt</a>, the White_Space in <a href=
- * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt
- * </a> or category Cc.
- * <p>
- * 2. The character is not supported in the current system font.
- * {@link android.graphics.Paint#hasGlyph(String)}
- * <p>
- *
- */
- private static boolean isTrimmable(@NonNull Paint paint, @NonNull CharSequence ch) {
- Objects.requireNonNull(paint);
- Objects.requireNonNull(ch);
-
- // if ch is empty or it is not a character (i,e, the count of code
- // point doesn't equal one), return false
- if (TextUtils.isEmpty(ch)
- || Character.codePointCount(ch, /* beginIndex= */ 0, ch.length()) != 1) {
- return false;
- }
-
- // Return true for the cases as below:
- // 1. The character is in the TRIMMABLE_CHARACTERS set
- // 2. The character is not supported in the system font
- return TRIMMABLE_CHARACTERS.contains(ch) || !paint.hasGlyph(ch.toString());
- }
-
- /**
- * If the {@code sequence} has some leading trimmable characters, creates a new copy
- * and removes the trimmable characters from the copy. Otherwise the given
- * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
- * to determine whether the character is trimmable or not.
- *
- * @return the trimmed string or the original string that has no
- * leading trimmable characters.
- * @see #isTrimmable(Paint, CharSequence)
- * @see #trim(CharSequence)
- * @see #trimEnd(CharSequence)
- *
- * @hide
- */
- @VisibleForTesting
- @NonNull
- public static CharSequence trimStart(@NonNull CharSequence sequence) {
- Objects.requireNonNull(sequence);
-
- if (TextUtils.isEmpty(sequence)) {
- return sequence;
- }
-
- final Paint paint = new Paint();
- int trimCount = 0;
- final int[] codePoints = sequence.codePoints().toArray();
- for (int i = 0, length = codePoints.length; i < length; i++) {
- String ch = Character.toString(codePoints[i]);
- if (!isTrimmable(paint, ch)) {
- break;
- }
- trimCount += ch.length();
- }
- if (trimCount == 0) {
- return sequence;
- }
- return sequence.subSequence(trimCount, sequence.length());
- }
-
- /**
- * If the {@code sequence} has some trailing trimmable characters, creates a new copy
- * and removes the trimmable characters from the copy. Otherwise the given
- * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
- * to determine whether the character is trimmable or not.
- *
- * @return the trimmed sequence or the original sequence that has no
- * trailing trimmable characters.
- * @see #isTrimmable(Paint, CharSequence)
- * @see #trimStart(CharSequence)
- * @see #trim(CharSequence)
- *
- * @hide
- */
- @VisibleForTesting
- @NonNull
- public static CharSequence trimEnd(@NonNull CharSequence sequence) {
- Objects.requireNonNull(sequence);
-
- if (TextUtils.isEmpty(sequence)) {
- return sequence;
- }
-
- final Paint paint = new Paint();
- int trimCount = 0;
- final int[] codePoints = sequence.codePoints().toArray();
- for (int i = codePoints.length - 1; i >= 0; i--) {
- String ch = Character.toString(codePoints[i]);
- if (!isTrimmable(paint, ch)) {
- break;
- }
- trimCount += ch.length();
- }
-
- if (trimCount == 0) {
- return sequence;
- }
- return sequence.subSequence(0, sequence.length() - trimCount);
- }
-
- /**
- * If the {@code sequence} has some leading or trailing trimmable characters, creates
- * a new copy and removes the trimmable characters from the copy. Otherwise the given
- * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)}
- * to determine whether the character is trimmable or not.
- *
- * @return the trimmed sequence or the original sequence that has no leading or
- * trailing trimmable characters.
- * @see #isTrimmable(Paint, CharSequence)
- * @see #trimStart(CharSequence)
- * @see #trimEnd(CharSequence)
- *
- * @hide
- */
- @VisibleForTesting
- @NonNull
- public static CharSequence trim(@NonNull CharSequence sequence) {
- Objects.requireNonNull(sequence);
-
- if (TextUtils.isEmpty(sequence)) {
- return sequence;
- }
-
- CharSequence result = trimStart(sequence);
- if (TextUtils.isEmpty(result)) {
- return result;
- }
-
- return trimEnd(result);
- }
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 884d463..f532c4c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -62,6 +62,8 @@
"mediaSharedWithParent";
private static final String ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT =
"credentialShareableWithParent";
+ private static final String ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE =
+ "authAlwaysRequiredToDisableQuietMode";
private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
@@ -80,6 +82,7 @@
INDEX_DELETE_APP_WITH_PARENT,
INDEX_ALWAYS_VISIBLE,
INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+ INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -97,6 +100,7 @@
private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
private static final int INDEX_ALWAYS_VISIBLE = 11;
private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
+ private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -329,6 +333,8 @@
setShowInSettings(orig.getShowInSettings());
setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
setUseParentsContacts(orig.getUseParentsContacts());
+ setAuthAlwaysRequiredToDisableQuietMode(
+ orig.isAuthAlwaysRequiredToDisableQuietMode());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -611,6 +617,31 @@
}
private boolean mCredentialShareableWithParent;
+ /**
+ * Returns whether the profile always requires user authentication to disable from quiet mode.
+ *
+ * <p> Settings this field to true will ensure that the credential confirmation activity is
+ * always shown whenever the user requests to disable quiet mode. The behavior of credential
+ * checks is not guaranteed when the property is false and may vary depending on user types.
+ * @hide
+ */
+ public boolean isAuthAlwaysRequiredToDisableQuietMode() {
+ if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+ return mAuthAlwaysRequiredToDisableQuietMode;
+ }
+ if (mDefaultProperties != null) {
+ return mDefaultProperties.mAuthAlwaysRequiredToDisableQuietMode;
+ }
+ throw new SecurityException(
+ "You don't have permission to query authAlwaysRequiredToDisableQuietMode");
+ }
+ /** @hide */
+ public void setAuthAlwaysRequiredToDisableQuietMode(boolean val) {
+ this.mAuthAlwaysRequiredToDisableQuietMode = val;
+ setPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE);
+ }
+ private boolean mAuthAlwaysRequiredToDisableQuietMode;
+
/*
Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
OTA update between user-parent
@@ -693,6 +724,8 @@
+ getCrossProfileIntentResolutionStrategy()
+ ", mMediaSharedWithParent=" + isMediaSharedWithParent()
+ ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
+ + ", mAuthAlwaysRequiredToDisableQuietMode="
+ + isAuthAlwaysRequiredToDisableQuietMode()
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ "}";
@@ -720,6 +753,8 @@
pw.println(prefix + " mMediaSharedWithParent=" + isMediaSharedWithParent());
pw.println(prefix + " mCredentialShareableWithParent="
+ isCredentialShareableWithParent());
+ pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode="
+ + isAuthAlwaysRequiredToDisableQuietMode());
pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent());
pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible());
}
@@ -788,6 +823,9 @@
case ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT:
setCredentialShareableWithParent(parser.getAttributeBoolean(i));
break;
+ case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
+ setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
+ break;
case ATTR_DELETE_APP_WITH_PARENT:
setDeleteAppWithParent(parser.getAttributeBoolean(i));
break;
@@ -853,6 +891,10 @@
serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT,
mCredentialShareableWithParent);
}
+ if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+ serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
+ mAuthAlwaysRequiredToDisableQuietMode);
+ }
if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
mDeleteAppWithParent);
@@ -878,6 +920,7 @@
dest.writeInt(mCrossProfileIntentResolutionStrategy);
dest.writeBoolean(mMediaSharedWithParent);
dest.writeBoolean(mCredentialShareableWithParent);
+ dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
dest.writeBoolean(mDeleteAppWithParent);
dest.writeBoolean(mAlwaysVisible);
}
@@ -901,6 +944,7 @@
mCrossProfileIntentResolutionStrategy = source.readInt();
mMediaSharedWithParent = source.readBoolean();
mCredentialShareableWithParent = source.readBoolean();
+ mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
mDeleteAppWithParent = source.readBoolean();
mAlwaysVisible = source.readBoolean();
}
@@ -941,6 +985,7 @@
CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
private boolean mMediaSharedWithParent = false;
private boolean mCredentialShareableWithParent = false;
+ private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
private boolean mDeleteAppWithParent = false;
private boolean mAlwaysVisible = false;
@@ -1010,6 +1055,14 @@
return this;
}
+ /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */
+ public Builder setAuthAlwaysRequiredToDisableQuietMode(
+ boolean authAlwaysRequiredToDisableQuietMode) {
+ mAuthAlwaysRequiredToDisableQuietMode =
+ authAlwaysRequiredToDisableQuietMode;
+ return this;
+ }
+
/** Sets the value for {@link #mDeleteAppWithParent}*/
public Builder setDeleteAppWithParent(boolean deleteAppWithParent) {
mDeleteAppWithParent = deleteAppWithParent;
@@ -1036,6 +1089,7 @@
mCrossProfileIntentResolutionStrategy,
mMediaSharedWithParent,
mCredentialShareableWithParent,
+ mAuthAlwaysRequiredToDisableQuietMode,
mDeleteAppWithParent,
mAlwaysVisible);
}
@@ -1053,6 +1107,7 @@
@CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
boolean mediaSharedWithParent,
boolean credentialShareableWithParent,
+ boolean authAlwaysRequiredToDisableQuietMode,
boolean deleteAppWithParent,
boolean alwaysVisible) {
mDefaultProperties = null;
@@ -1067,6 +1122,8 @@
setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
setMediaSharedWithParent(mediaSharedWithParent);
setCredentialShareableWithParent(credentialShareableWithParent);
+ setAuthAlwaysRequiredToDisableQuietMode(
+ authAlwaysRequiredToDisableQuietMode);
setDeleteAppWithParent(deleteAppWithParent);
setAlwaysVisible(alwaysVisible);
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 62630c8..d274792 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -163,6 +163,7 @@
GRAMMATICAL_GENDER_FEMININE,
GRAMMATICAL_GENDER_MASCULINE,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface GrammaticalGender {}
/**
@@ -698,6 +699,7 @@
ORIENTATION_LANDSCAPE,
ORIENTATION_SQUARE
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Orientation {
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index 90bbca8..f82f79e 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -131,6 +131,7 @@
BIOMETRIC_CONVENIENCE,
DEVICE_CREDENTIAL,
})
+ @Retention(RetentionPolicy.SOURCE)
@interface Types {}
/**
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index ea951a5..a978bd8 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -858,6 +858,7 @@
switch(format) {
case ImageFormat.YUV_420_888:
case ImageFormat.JPEG:
+ case ImageFormat.JPEG_R:
break;
default:
throw new IllegalArgumentException("Unsupported format: " + format);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index f4fc472..a8066aa 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -47,7 +47,8 @@
public static final int[] SUPPORTED_CAPTURE_OUTPUT_FORMATS = {
CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT,
- ImageFormat.JPEG
+ ImageFormat.JPEG,
+ ImageFormat.JPEG_R
};
public static class SurfaceInfo {
@@ -92,6 +93,10 @@
(dataspace == StreamConfigurationMap.HAL_DATASPACE_V0_JFIF)) {
surfaceInfo.mFormat = ImageFormat.JPEG;
return surfaceInfo;
+ } else if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB)
+ && (dataspace == StreamConfigurationMap.HAL_DATASPACE_JPEG_R)) {
+ surfaceInfo.mFormat = ImageFormat.JPEG_R;
+ return surfaceInfo;
}
return surfaceInfo;
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index b0b7a41..440585c 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -159,6 +159,7 @@
RESULT_INCORRECT_MODE,
RESULT_COMMUNICATION_FAILED,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ControlCallbackResult {}
/** Control operation is successfully handled by the framework. */
@@ -1135,6 +1136,7 @@
CEC_SETTING_NAME_QUERY_SAD_MAX,
SETTING_NAME_EARC_ENABLED,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface SettingName {}
/**
@@ -1157,6 +1159,7 @@
CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
CEC_SETTING_NAME_QUERY_SAD_MAX,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface CecSettingSad {}
// True if we have a logical device of type playback hosted in the system.
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 01ce7b9..481ec72 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,8 @@
*/
package android.hardware.location;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -1111,12 +1113,12 @@
}
};
- /** @throws ServiceNotFoundException
- * @hide */
- public ContextHubManager(Context context, Looper mainLooper) throws ServiceNotFoundException {
+ /** @hide */
+ public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) {
+ requireNonNull(service, "service cannot be null");
+ requireNonNull(mainLooper, "mainLooper cannot be null");
+ mService = service;
mMainLooper = mainLooper;
- mService = IContextHubService.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.CONTEXTHUB_SERVICE));
try {
mService.registerCallback(mClientCallback);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 889d3df..81a0234 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -52,6 +52,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -738,6 +740,7 @@
FUNCTION_NCM,
FUNCTION_UVC,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface UsbFunctionMode {}
/** @hide */
@@ -748,6 +751,7 @@
GADGET_HAL_V1_2,
GADGET_HAL_V2_0,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface UsbGadgetHalVersion {}
/** @hide */
@@ -759,6 +763,7 @@
USB_HAL_V1_3,
USB_HAL_V2_0,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface UsbHalVersion {}
/**
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 5c5083a..63ae28f 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -13,3 +13,10 @@
description: "Flag incompatible charging on COMPLIANCE_WARNING_INPUT_POWER_LIMITED instead of COMPLIANCE_WARNING_OTHER when enabled"
bug: "308700954"
}
+
+flag {
+ name: "enable_report_usb_data_compliance_warning"
+ namespace: "system_sw_usb"
+ description: "Enable reporting USB data compliance warnings from HAL when set"
+ bug: "296119135"
+}
diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java
index e06b0f9..65ed618 100644
--- a/core/java/android/os/DeadObjectException.java
+++ b/core/java/android/os/DeadObjectException.java
@@ -19,7 +19,8 @@
/**
* The object you are calling has died, because its hosting process
- * no longer exists.
+ * no longer exists. This is also thrown for low-level binder
+ * errors.
*/
public class DeadObjectException extends RemoteException {
public DeadObjectException() {
diff --git a/core/java/android/os/DeadSystemRuntimeException.java b/core/java/android/os/DeadSystemRuntimeException.java
index 1e86924..82b1ad8 100644
--- a/core/java/android/os/DeadSystemRuntimeException.java
+++ b/core/java/android/os/DeadSystemRuntimeException.java
@@ -18,9 +18,10 @@
/**
* Exception thrown when a call into system_server resulted in a
- * DeadObjectException, meaning that the system_server has died. There's
- * nothing apps can do at this point - the system will automatically restart -
- * so there's no point in catching this.
+ * DeadObjectException, meaning that the system_server has died or
+ * experienced a low-level binder error. There's * nothing apps can
+ * do at this point - the system will automatically restart - so
+ * there's no point in catching this.
*
* @hide
*/
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 6b43e73..fe85da2 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -17,6 +17,8 @@
package android.os;
+import android.os.WorkDuration;
+
/** {@hide} */
oneway interface IHintSession {
void updateTargetWorkDuration(long targetDurationNanos);
@@ -24,4 +26,5 @@
void close();
void sendHint(int hint);
void setMode(int mode, boolean enabled);
+ void reportActualWorkDuration2(in WorkDuration[] workDurations);
}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 11084b8..e005910 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -103,7 +103,7 @@
* Any call in this class will change its internal data, so you must do your own thread
* safety to protect from racing.
*
- * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ * All timings should be in {@link SystemClock#uptimeNanos()}.
*/
public static class Session implements Closeable {
private long mNativeSessionPtr;
@@ -269,6 +269,40 @@
public @Nullable int[] getThreadIds() {
return nativeGetThreadIds(mNativeSessionPtr);
}
+
+ /**
+ * Reports the work duration for the last cycle of work.
+ *
+ * The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.
+ *
+ * @param workDuration the work duration of each component.
+ * @throws IllegalArgumentException if work period start timestamp is not positive, or
+ * actual total duration is not positive, or actual CPU duration is not positive,
+ * or actual GPU duration is negative.
+ */
+ @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+ public void reportActualWorkDuration(@NonNull WorkDuration workDuration) {
+ if (workDuration.mWorkPeriodStartTimestampNanos <= 0) {
+ throw new IllegalArgumentException(
+ "the work period start timestamp should be positive.");
+ }
+ if (workDuration.mActualTotalDurationNanos <= 0) {
+ throw new IllegalArgumentException("the actual total duration should be positive.");
+ }
+ if (workDuration.mActualCpuDurationNanos <= 0) {
+ throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ }
+ if (workDuration.mActualGpuDurationNanos < 0) {
+ throw new IllegalArgumentException(
+ "the actual GPU duration should be non negative.");
+ }
+ nativeReportActualWorkDuration(mNativeSessionPtr,
+ workDuration.mWorkPeriodStartTimestampNanos,
+ workDuration.mActualTotalDurationNanos,
+ workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos);
+ }
}
private static native long nativeAcquireManager();
@@ -285,4 +319,7 @@
private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
boolean enabled);
+ private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
+ long workPeriodStartTimestampNanos, long actualTotalDurationNanos,
+ long actualCpuDurationNanos, long actualGpuDurationNanos);
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index d4688f8..f71c269 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -57,6 +57,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.SignatureException;
@@ -166,6 +168,7 @@
RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED,
RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH,
RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ResumeOnRebootRebootErrorCode {}
/**
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 49a0bd3..e2a5833 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.app.time.UnixEpochTime;
@@ -202,8 +203,8 @@
* Returns nanoseconds since boot, not counting time spent in deep sleep.
*
* @return nanoseconds of non-sleep uptime since boot.
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
@CriticalNative
@android.ravenwood.annotation.RavenwoodReplace
public static native long uptimeNanos();
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index b7e3068..0a8f62f 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -25,6 +25,9 @@
import android.os.IUpdateEngineCallback;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* UpdateEngine handles calls to the update engine which takes care of A/B OTA
* updates. It wraps up the update engine Binder APIs and exposes them as
@@ -178,6 +181,7 @@
ErrorCodeConstants.NOT_ENOUGH_SPACE,
ErrorCodeConstants.DEVICE_CORRUPTED,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ErrorCode {}
/**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d9c6bee..2419a4c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -204,6 +204,8 @@
* the user in locked state so that a direct boot aware DPC could reset the password.
* Should not be used together with
* {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED} or an exception will be thrown.
+ * This flag is currently only allowed for {@link #isManagedProfile() managed profiles};
+ * usage on other profiles may result in an Exception.
* @hide
*/
public static final int QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL = 0x2;
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/os/WorkDuration.aidl
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to core/java/android/os/WorkDuration.aidl
index 2529807..0f61204 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/core/java/android/os/WorkDuration.aidl
@@ -13,16 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package android.os;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file
diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java
new file mode 100644
index 0000000..4fdc34f
--- /dev/null
+++ b/core/java/android/os/WorkDuration.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * WorkDuration contains the measured time in nano seconds of the workload
+ * in each component, see
+ * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION)
+public final class WorkDuration implements Parcelable {
+ long mWorkPeriodStartTimestampNanos = 0;
+ long mActualTotalDurationNanos = 0;
+ long mActualCpuDurationNanos = 0;
+ long mActualGpuDurationNanos = 0;
+ long mTimestampNanos = 0;
+
+ public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() {
+ @Override
+ public WorkDuration createFromParcel(Parcel in) {
+ return new WorkDuration(in);
+ }
+
+ @Override
+ public WorkDuration[] newArray(int size) {
+ return new WorkDuration[size];
+ }
+ };
+
+ public WorkDuration() {}
+
+ public WorkDuration(long workPeriodStartTimestampNanos,
+ long actualTotalDurationNanos,
+ long actualCpuDurationNanos,
+ long actualGpuDurationNanos) {
+ mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+ mActualTotalDurationNanos = actualTotalDurationNanos;
+ mActualCpuDurationNanos = actualCpuDurationNanos;
+ mActualGpuDurationNanos = actualGpuDurationNanos;
+ }
+
+ /**
+ * @hide
+ */
+ public WorkDuration(long workPeriodStartTimestampNanos,
+ long actualTotalDurationNanos,
+ long actualCpuDurationNanos,
+ long actualGpuDurationNanos,
+ long timestampNanos) {
+ mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+ mActualTotalDurationNanos = actualTotalDurationNanos;
+ mActualCpuDurationNanos = actualCpuDurationNanos;
+ mActualGpuDurationNanos = actualGpuDurationNanos;
+ mTimestampNanos = timestampNanos;
+ }
+
+ WorkDuration(@NonNull Parcel in) {
+ mWorkPeriodStartTimestampNanos = in.readLong();
+ mActualTotalDurationNanos = in.readLong();
+ mActualCpuDurationNanos = in.readLong();
+ mActualGpuDurationNanos = in.readLong();
+ mTimestampNanos = in.readLong();
+ }
+
+ /**
+ * Sets the work period start timestamp in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) {
+ if (workPeriodStartTimestampNanos <= 0) {
+ throw new IllegalArgumentException(
+ "the work period start timestamp should be positive.");
+ }
+ mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos;
+ }
+
+ /**
+ * Sets the actual total duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public void setActualTotalDurationNanos(long actualTotalDurationNanos) {
+ if (actualTotalDurationNanos <= 0) {
+ throw new IllegalArgumentException("the actual total duration should be positive.");
+ }
+ mActualTotalDurationNanos = actualTotalDurationNanos;
+ }
+
+ /**
+ * Sets the actual CPU duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public void setActualCpuDurationNanos(long actualCpuDurationNanos) {
+ if (actualCpuDurationNanos <= 0) {
+ throw new IllegalArgumentException("the actual CPU duration should be positive.");
+ }
+ mActualCpuDurationNanos = actualCpuDurationNanos;
+ }
+
+ /**
+ * Sets the actual GPU duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public void setActualGpuDurationNanos(long actualGpuDurationNanos) {
+ if (actualGpuDurationNanos < 0) {
+ throw new IllegalArgumentException("the actual GPU duration should be non negative.");
+ }
+ mActualGpuDurationNanos = actualGpuDurationNanos;
+ }
+
+ /**
+ * Returns the work period start timestamp based in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public long getWorkPeriodStartTimestampNanos() {
+ return mWorkPeriodStartTimestampNanos;
+ }
+
+ /**
+ * Returns the actual total duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public long getActualTotalDurationNanos() {
+ return mActualTotalDurationNanos;
+ }
+
+ /**
+ * Returns the actual CPU duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public long getActualCpuDurationNanos() {
+ return mActualCpuDurationNanos;
+ }
+
+ /**
+ * Returns the actual GPU duration in nanoseconds.
+ *
+ * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
+ */
+ public long getActualGpuDurationNanos() {
+ return mActualGpuDurationNanos;
+ }
+
+ /**
+ * @hide
+ */
+ public long getTimestampNanos() {
+ return mTimestampNanos;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mWorkPeriodStartTimestampNanos);
+ dest.writeLong(mActualTotalDurationNanos);
+ dest.writeLong(mActualCpuDurationNanos);
+ dest.writeLong(mActualGpuDurationNanos);
+ dest.writeLong(mTimestampNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof WorkDuration)) {
+ return false;
+ }
+ WorkDuration workDuration = (WorkDuration) obj;
+ return workDuration.mTimestampNanos == this.mTimestampNanos
+ && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos
+ && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos
+ && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos
+ && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos,
+ mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos);
+ }
+}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 940ddf2..0809b3b 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -54,4 +54,11 @@
namespace: "backstage_power"
description: "Guards a new API in PowerManager to check if battery saver is supported or not."
bug: "305067031"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "adpf_gpu_report_actual_work_duration"
+ namespace: "game"
+ description: "Guards the ADPF GPU APIs."
+ bug: "284324521"
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d1802a..6853892 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2133,6 +2133,7 @@
MOUNT_MODE_EXTERNAL_PASS_THROUGH,
MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
})
+ @Retention(RetentionPolicy.SOURCE)
/** @hide */
public @interface MountMode {}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4e7734c..33c15d77 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12817,15 +12817,6 @@
"render_shadows_in_compositor";
/**
- * If true, submit buffers using blast in ViewRootImpl.
- * (0 = false, 1 = true)
- * @hide
- */
- @Readable
- public static final String DEVELOPMENT_USE_BLAST_ADAPTER_VR =
- "use_blast_adapter_vr";
-
- /**
* Path to the WindowManager display settings file. If unset, the default file path will
* be used.
*
@@ -19472,6 +19463,7 @@
*
* @hide
*/
+ @Readable
public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package";
/**
@@ -19479,6 +19471,7 @@
*
* @hide
*/
+ @Readable
public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package";
/*
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 759953e..92c516c 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -309,6 +309,7 @@
REASON_ASSISTANT_CANCEL,
REASON_LOCKDOWN,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface NotificationCancelReason{};
/**
@@ -320,6 +321,7 @@
FLAG_FILTER_TYPE_SILENT,
FLAG_FILTER_TYPE_ONGOING
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface NotificationFilterTypes {}
/**
* A flag value indicating that this notification listener can see conversation type
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
new file mode 100644
index 0000000..5b096c6
--- /dev/null
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Represents the set of device effects (affecting display and device behavior in general) that
+ * are applied whenever an {@link android.app.AutomaticZenRule} is active.
+ */
+@FlaggedApi(Flags.FLAG_MODES_API)
+public final class ZenDeviceEffects implements Parcelable {
+
+ private final boolean mGrayscale;
+ private final boolean mSuppressAmbientDisplay;
+ private final boolean mDimWallpaper;
+ private final boolean mNightMode;
+
+ private final boolean mDisableAutoBrightness;
+ private final boolean mDisableTapToWake;
+ private final boolean mDisableTiltToWake;
+ private final boolean mDisableTouch;
+ private final boolean mMinimizeRadioUsage;
+ private final boolean mMaximizeDoze;
+
+ private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
+ boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
+ boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
+ boolean minimizeRadioUsage, boolean maximizeDoze) {
+ mGrayscale = grayscale;
+ mSuppressAmbientDisplay = suppressAmbientDisplay;
+ mDimWallpaper = dimWallpaper;
+ mNightMode = nightMode;
+ mDisableAutoBrightness = disableAutoBrightness;
+ mDisableTapToWake = disableTapToWake;
+ mDisableTiltToWake = disableTiltToWake;
+ mDisableTouch = disableTouch;
+ mMinimizeRadioUsage = minimizeRadioUsage;
+ mMaximizeDoze = maximizeDoze;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof final ZenDeviceEffects that)) return false;
+ if (obj == this) return true;
+
+ return this.mGrayscale == that.mGrayscale
+ && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay
+ && this.mDimWallpaper == that.mDimWallpaper
+ && this.mNightMode == that.mNightMode
+ && this.mDisableAutoBrightness == that.mDisableAutoBrightness
+ && this.mDisableTapToWake == that.mDisableTapToWake
+ && this.mDisableTiltToWake == that.mDisableTiltToWake
+ && this.mDisableTouch == that.mDisableTouch
+ && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
+ && this.mMaximizeDoze == that.mMaximizeDoze;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
+ mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
+ mMinimizeRadioUsage, mMaximizeDoze);
+ }
+
+ @Override
+ public String toString() {
+ ArrayList<String> effects = new ArrayList<>(10);
+ if (mGrayscale) effects.add("grayscale");
+ if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay");
+ if (mDimWallpaper) effects.add("dimWallpaper");
+ if (mNightMode) effects.add("nightMode");
+ if (mDisableAutoBrightness) effects.add("disableAutoBrightness");
+ if (mDisableTapToWake) effects.add("disableTapToWake");
+ if (mDisableTiltToWake) effects.add("disableTiltToWake");
+ if (mDisableTouch) effects.add("disableTouch");
+ if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
+ if (mMaximizeDoze) effects.add("maximizeDoze");
+ return "[" + String.join(", ", effects) + "]";
+ }
+
+ /**
+ * Whether the level of color saturation of the display should be set to minimum, effectively
+ * switching it to grayscale, while the rule is active.
+ */
+ public boolean shouldDisplayGrayscale() {
+ return mGrayscale;
+ }
+
+ /**
+ * Whether the ambient (always-on) display feature should be disabled while the rule is active.
+ * This will have no effect if the device doesn't support always-on display or if it's not
+ * generally enabled.
+ */
+ public boolean shouldSuppressAmbientDisplay() {
+ return mSuppressAmbientDisplay;
+ }
+
+ /** Whether the wallpaper should be dimmed while the rule is active. */
+ public boolean shouldDimWallpaper() {
+ return mDimWallpaper;
+ }
+
+ /** Whether night mode (aka dark theme) should be applied while the rule is active. */
+ public boolean shouldUseNightMode() {
+ return mNightMode;
+ }
+
+ /**
+ * Whether the display's automatic brightness adjustment should be disabled while the rule is
+ * active.
+ * @hide
+ */
+ public boolean shouldDisableAutoBrightness() {
+ return mDisableAutoBrightness;
+ }
+
+ /**
+ * Whether "tap to wake" should be disabled while the rule is active.
+ * @hide
+ */
+ public boolean shouldDisableTapToWake() {
+ return mDisableTapToWake;
+ }
+
+ /**
+ * Whether "tilt to wake" should be disabled while the rule is active.
+ * @hide
+ */
+ public boolean shouldDisableTiltToWake() {
+ return mDisableTiltToWake;
+ }
+
+ /**
+ * Whether touch interactions should be disabled while the rule is active.
+ * @hide
+ */
+ public boolean shouldDisableTouch() {
+ return mDisableTouch;
+ }
+
+ /**
+ * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be
+ * minimized while the rule is active.
+ * @hide
+ */
+ public boolean shouldMinimizeRadioUsage() {
+ return mMinimizeRadioUsage;
+ }
+
+ /**
+ * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent
+ * maintenance windows) while the rule is active.
+ * @hide
+ */
+ public boolean shouldMaximizeDoze() {
+ return mMaximizeDoze;
+ }
+
+ /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */
+ @NonNull
+ public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() {
+ @Override
+ public ZenDeviceEffects createFromParcel(Parcel in) {
+ return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(),
+ in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
+ in.readBoolean(), in.readBoolean(), in.readBoolean());
+ }
+
+ @Override
+ public ZenDeviceEffects[] newArray(int size) {
+ return new ZenDeviceEffects[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mGrayscale);
+ dest.writeBoolean(mSuppressAmbientDisplay);
+ dest.writeBoolean(mDimWallpaper);
+ dest.writeBoolean(mNightMode);
+ dest.writeBoolean(mDisableAutoBrightness);
+ dest.writeBoolean(mDisableTapToWake);
+ dest.writeBoolean(mDisableTiltToWake);
+ dest.writeBoolean(mDisableTouch);
+ dest.writeBoolean(mMinimizeRadioUsage);
+ dest.writeBoolean(mMaximizeDoze);
+ }
+
+ /** Builder class for {@link ZenDeviceEffects} objects. */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final class Builder {
+
+ private boolean mGrayscale;
+ private boolean mSuppressAmbientDisplay;
+ private boolean mDimWallpaper;
+ private boolean mNightMode;
+ private boolean mDisableAutoBrightness;
+ private boolean mDisableTapToWake;
+ private boolean mDisableTiltToWake;
+ private boolean mDisableTouch;
+ private boolean mMinimizeRadioUsage;
+ private boolean mMaximizeDoze;
+
+ /**
+ * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
+ */
+ public Builder() {
+ }
+
+ /**
+ * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding
+ * values in the supplied {@link ZenDeviceEffects}.
+ */
+ public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) {
+ mGrayscale = zenDeviceEffects.shouldDisplayGrayscale();
+ mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay();
+ mDimWallpaper = zenDeviceEffects.shouldDimWallpaper();
+ mNightMode = zenDeviceEffects.shouldUseNightMode();
+ mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness();
+ mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake();
+ mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake();
+ mDisableTouch = zenDeviceEffects.shouldDisableTouch();
+ mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
+ mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
+ }
+
+ /**
+ * Sets whether the level of color saturation of the display should be set to minimum,
+ * effectively switching it to grayscale, while the rule is active.
+ */
+ @NonNull
+ public Builder setShouldDisplayGrayscale(boolean grayscale) {
+ mGrayscale = grayscale;
+ return this;
+ }
+
+ /**
+ * Sets whether the ambient (always-on) display feature should be disabled while the rule
+ * is active. This will have no effect if the device doesn't support always-on display or if
+ * it's not generally enabled.
+ */
+ @NonNull
+ public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) {
+ mSuppressAmbientDisplay = suppressAmbientDisplay;
+ return this;
+ }
+
+ /** Sets whether the wallpaper should be dimmed while the rule is active. */
+ @NonNull
+ public Builder setShouldDimWallpaper(boolean dimWallpaper) {
+ mDimWallpaper = dimWallpaper;
+ return this;
+ }
+
+ /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */
+ @NonNull
+ public Builder setShouldUseNightMode(boolean nightMode) {
+ mNightMode = nightMode;
+ return this;
+ }
+
+ /**
+ * Sets whether the display's automatic brightness adjustment should be disabled while the
+ * rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) {
+ mDisableAutoBrightness = disableAutoBrightness;
+ return this;
+ }
+
+ /**
+ * Sets whether "tap to wake" should be disabled while the rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldDisableTapToWake(boolean disableTapToWake) {
+ mDisableTapToWake = disableTapToWake;
+ return this;
+ }
+
+ /**
+ * Sets whether "tilt to wake" should be disabled while the rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) {
+ mDisableTiltToWake = disableTiltToWake;
+ return this;
+ }
+
+ /**
+ * Sets whether touch interactions should be disabled while the rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldDisableTouch(boolean disableTouch) {
+ mDisableTouch = disableTouch;
+ return this;
+ }
+
+ /**
+ * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption,
+ * should be minimized while the rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) {
+ mMinimizeRadioUsage = minimizeRadioUsage;
+ return this;
+ }
+
+ /**
+ * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less
+ * frequent maintenance windows) while the rule is active.
+ * @hide
+ */
+ @NonNull
+ public Builder setShouldMaximizeDoze(boolean maximizeDoze) {
+ mMaximizeDoze = maximizeDoze;
+ return this;
+ }
+
+ /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
+ @NonNull
+ public ZenDeviceEffects build() {
+ return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper,
+ mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake,
+ mDisableTouch, mMinimizeRadioUsage, mMaximizeDoze);
+ }
+ }
+}
diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java
index ff6dffd..1e08fd8 100644
--- a/core/java/android/service/voice/HotwordDetectedResult.java
+++ b/core/java/android/service/voice/HotwordDetectedResult.java
@@ -85,6 +85,7 @@
CONFIDENCE_LEVEL_HIGH,
CONFIDENCE_LEVEL_VERY_HIGH
})
+ @Retention(RetentionPolicy.SOURCE)
@interface HotwordConfidenceLevelValue {
}
diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java
index 7b3f47d..26c1ca4 100644
--- a/core/java/android/service/voice/HotwordRejectedResult.java
+++ b/core/java/android/service/voice/HotwordRejectedResult.java
@@ -22,6 +22,9 @@
import com.android.internal.util.DataClass;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Represents a result supporting the rejected hotword trigger.
*
@@ -57,6 +60,7 @@
CONFIDENCE_LEVEL_MEDIUM,
CONFIDENCE_LEVEL_HIGH
})
+ @Retention(RetentionPolicy.SOURCE)
@interface HotwordConfidenceLevelValue {
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 04ae0af..bf09ec1 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -273,8 +273,8 @@
int mCurHeight;
float mZoom = 0f;
int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS
- | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+ int mWindowPrivateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
int mCurWindowFlags = mWindowFlags;
int mCurWindowPrivateFlags = mWindowPrivateFlags;
Rect mPreviewSurfacePosition;
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java
index 35834fd..e6fcc0c 100644
--- a/core/java/android/speech/SpeechRecognizer.java
+++ b/core/java/android/speech/SpeechRecognizer.java
@@ -38,6 +38,7 @@
import android.os.ServiceManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.CloseGuard;
import android.util.Log;
import android.util.Slog;
@@ -46,6 +47,7 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.Reference;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
@@ -59,6 +61,9 @@
* {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be
* invoked only from the main application thread.
*
+ * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+ * SpeechRecognizer object when it is no longer needed.
+ *
* <p>The implementation of this API is likely to stream audio to remote servers to perform speech
* recognition. As such this API is not intended to be used for continuous recognition, which would
* consume a significant amount of battery and bandwidth.
@@ -301,6 +306,8 @@
/** The actual RecognitionService endpoint */
private IRecognitionService mService;
+ private final CloseGuard mCloseGuard = new CloseGuard();
+
/** Context with which the manager was created */
private final Context mContext;
@@ -366,9 +373,7 @@
* {@link #createSpeechRecognizer} static factory method
*/
private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
- mContext = context;
- mServiceComponent = serviceComponent;
- mOnDevice = false;
+ this(context, serviceComponent, false);
}
/**
@@ -376,9 +381,17 @@
* {@link #createOnDeviceSpeechRecognizer} static factory method
*/
private SpeechRecognizer(final Context context, boolean onDevice) {
+ this(context, null, onDevice);
+ }
+
+ private SpeechRecognizer(
+ final Context context,
+ final ComponentName serviceComponent,
+ final boolean onDevice) {
mContext = context;
- mServiceComponent = null;
+ mServiceComponent = serviceComponent;
mOnDevice = onDevice;
+ mCloseGuard.open("SpeechRecognizer#destroy()");
}
/**
@@ -418,6 +431,9 @@
* command to the created {@code SpeechRecognizer}, otherwise no notifications will be
* received.
*
+ * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+ * SpeechRecognizer object when it is no longer needed.
+ *
* <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition
* service requires <queries> element to be added to the manifest file:
* <pre>{@code
@@ -445,6 +461,9 @@
* Use this version of the method to specify a specific service to direct this
* {@link SpeechRecognizer} to.
*
+ * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+ * SpeechRecognizer object when it is no longer needed.
+ *
* <p><strong>Important</strong>: before calling this method, please check via
* {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code
* serviceComponent} actually exists and provides
@@ -486,6 +505,9 @@
* before dispatching any command to the created {@code SpeechRecognizer}, otherwise no
* notifications will be received.
*
+ * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a
+ * SpeechRecognizer object when it is no longer needed.
+ *
* @param context in which to create {@code SpeechRecognizer}
* @return a new on-device {@code SpeechRecognizer}.
* @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)}
@@ -870,7 +892,7 @@
}
private boolean checkOpenConnection() {
- if (mService != null) {
+ if (mService != null && mService.asBinder().isBinderAlive()) {
return true;
}
mListener.onError(ERROR_CLIENT);
@@ -886,17 +908,32 @@
/** Destroys the {@code SpeechRecognizer} object. */
public void destroy() {
- if (mService != null) {
- try {
- mService.cancel(mListener, /*isShutdown*/ true);
- } catch (final Exception e) {
- // Not important
+ try {
+ if (mService != null) {
+ try {
+ mService.cancel(mListener, /*isShutdown*/ true);
+ } catch (final Exception e) {
+ // Not important
+ }
}
- }
- mService = null;
- mPendingTasks.clear();
- mListener.mInternalListener = null;
+ mService = null;
+ mPendingTasks.clear();
+ mListener.mInternalListener = null;
+ mCloseGuard.close();
+ } finally {
+ Reference.reachabilityFence(this);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ mCloseGuard.warnIfOpen();
+ destroy();
+ } finally {
+ super.finalize();
+ }
}
/** Establishes a connection to system server proxy and initializes the session. */
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6c3b8ab..17bbee6d0 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -114,8 +114,6 @@
IWindowSession openSession(in IWindowSessionCallback callback);
- boolean useBLAST();
-
@UnsupportedAppUsage
void getInitialDisplaySize(int displayId, out Point size);
@UnsupportedAppUsage
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 02e97da..d6535d4 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -296,9 +296,11 @@
/**
* Request the server to call setInputWindowInfo on a given Surface, and return
- * an input channel where the client can receive input.
+ * an input channel where the client can receive input. For windows, the clientToken should be
+ * the IWindow binder object. For other requests, the token can be any unique IBinder token to
+ * be used as unique identifier.
*/
- void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window,
+ void grantInputChannel(int displayId, in SurfaceControl surface, in IBinder clientToken,
in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName,
out InputChannel outInputChannel);
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 45b3fdd..7e388d4 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -26,6 +26,7 @@
import android.gui.TouchOcclusionMode;
import android.os.IBinder;
import android.os.InputConfig;
+import android.util.Size;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -82,15 +83,11 @@
public IBinder token;
/**
- * The {@link IWindow} handle if InputWindowHandle is associated with a window, null otherwise.
+ * The {@link IBinder} handle if InputWindowHandle is associated with a client token,
+ * normally the IWindow token, null otherwise.
*/
@Nullable
private IBinder windowToken;
- /**
- * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow
- * is called.
- */
- private IWindow window;
// The window name.
public String name;
@@ -106,6 +103,9 @@
// Window frame.
public final Rect frame = new Rect();
+ // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+ public Size contentSize = new Size(0, 0);
+
public int surfaceInset;
// Global scaling factor applied to touch events when they are dispatched
@@ -177,7 +177,6 @@
inputApplicationHandle = new InputApplicationHandle(other.inputApplicationHandle);
token = other.token;
windowToken = other.windowToken;
- window = other.window;
name = other.name;
layoutParamsFlags = other.layoutParamsFlags;
layoutParamsType = other.layoutParamsType;
@@ -199,6 +198,7 @@
transform.set(other.transform);
}
focusTransferTarget = other.focusTransferTarget;
+ contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight());
}
@Override
@@ -211,6 +211,7 @@
.append(", windowToken=").append(windowToken)
.append(", displayId=").append(displayId)
.append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
+ .append(", contentSize=").append(contentSize)
.toString();
}
@@ -243,23 +244,14 @@
touchableRegionSurfaceControl = new WeakReference<>(bounds);
}
- public void setWindowToken(IWindow iwindow) {
- windowToken = iwindow.asBinder();
- window = iwindow;
+ public void setWindowToken(IBinder iwindow) {
+ windowToken = iwindow;
}
public @Nullable IBinder getWindowToken() {
return windowToken;
}
- public IWindow getWindow() {
- if (window != null) {
- return window;
- }
- window = IWindow.Stub.asInterface(windowToken);
- return window;
- }
-
/**
* Set the provided inputConfig flag values.
* @param inputConfig the flag values to change
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b17d2d1..c6601e8d 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2065,6 +2065,7 @@
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN:
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT:
+ case KeyEvent.KEYCODE_STEM_PRIMARY:
return true;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ac2c550..145af2e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -442,9 +442,6 @@
*/
private boolean mForceNextConfigUpdate;
- private boolean mUseBLASTAdapter;
- private boolean mForceDisableBLAST;
-
/** lazily-initialized in getAudioManager() */
private boolean mFastScrollSoundEffectsEnabled = false;
@@ -1290,8 +1287,6 @@
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
- mWindowAttributes.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
attrs = mWindowAttributes;
setTag();
@@ -1502,9 +1497,6 @@
Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: "
+ mAttachInfo.mDisplayState, new Throwable());
}
- if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
- mUseBLASTAdapter = true;
- }
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
@@ -1901,8 +1893,7 @@
mWindowAttributes.insetsFlags.appearance = appearance;
mWindowAttributes.insetsFlags.behavior = behavior;
mWindowAttributes.privateFlags |= compatibleWindowFlag
- | appearanceAndBehaviorPrivateFlags
- | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+ | appearanceAndBehaviorPrivateFlags;
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
@@ -8692,11 +8683,7 @@
}
if (mSurfaceControl.isValid()) {
- if (!useBLAST()) {
- mSurface.copyFrom(mSurfaceControl);
- } else {
- updateBlastSurfaceIfNeeded();
- }
+ updateBlastSurfaceIfNeeded();
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
}
@@ -11533,7 +11520,7 @@
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setBlurRegions(surfaceControl, regionCopy);
- if (useBLAST() && mBlastBufferQueue != null) {
+ if (mBlastBufferQueue != null) {
mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
}
}
@@ -11550,18 +11537,6 @@
mUnbufferedInputSource = mView.mUnbufferedInputSource;
}
- /**
- * Force disabling use of the BLAST adapter regardless of the system
- * flag. Needs to be called before addView.
- */
- void forceDisableBLAST() {
- mForceDisableBLAST = true;
- }
-
- boolean useBLAST() {
- return mUseBLASTAdapter && !mForceDisableBLAST;
- }
-
int getSurfaceSequenceId() {
return mSurfaceSequenceId;
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c735142..92ce9f7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -651,6 +651,7 @@
REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY,
REMOVE_CONTENT_MODE_DESTROY,
})
+ @Retention(RetentionPolicy.SOURCE)
@interface RemoveContentMode {}
/**
@@ -685,6 +686,7 @@
DISPLAY_IME_POLICY_FALLBACK_DISPLAY,
DISPLAY_IME_POLICY_HIDE,
})
+ @Retention(RetentionPolicy.SOURCE)
@interface DisplayImePolicy {}
/**
@@ -3249,13 +3251,6 @@
public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24;
/**
- * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
- * If not specified the client will receive a BufferQueue layer.
- * @hide
- */
- public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25;
-
- /**
* Flag to indicate that the window is controlling the appearance of system bars. So we
* don't need to adjust it by reading its system UI flags for compatibility.
* @hide
@@ -3340,7 +3335,6 @@
PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
PRIVATE_FLAG_NOT_MAGNIFIABLE,
PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
- PRIVATE_FLAG_USE_BLAST,
PRIVATE_FLAG_APPEARANCE_CONTROLLED,
PRIVATE_FLAG_BEHAVIOR_CONTROLLED,
PRIVATE_FLAG_FIT_INSETS_CONTROLLED,
@@ -3349,6 +3343,7 @@
PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP,
PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface PrivateFlags {}
/**
@@ -3438,10 +3433,6 @@
equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
name = "COLOR_SPACE_AGNOSTIC"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_USE_BLAST,
- equals = PRIVATE_FLAG_USE_BLAST,
- name = "USE_BLAST"),
- @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED,
name = "APPEARANCE_CONTROLLED"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d20d95d..214f1ec 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -60,8 +60,6 @@
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
- private static boolean sUseBLASTAdapter = false;
-
/**
* This is the first time the window is being drawn,
* so the client must call drawingFinished() when done
@@ -99,7 +97,6 @@
public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
public static final int ADD_FLAG_APP_VISIBLE = 0x2;
- public static final int ADD_FLAG_USE_BLAST = 0x8;
/**
* Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the
@@ -176,7 +173,6 @@
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
- sUseBLASTAdapter = sWindowManagerService.useBLAST();
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -218,13 +214,6 @@
}
}
- /**
- * Whether or not to use BLAST for ViewRootImpl
- */
- public static boolean useBLAST() {
- return sUseBLASTAdapter;
- }
-
@UnsupportedAppUsage
public String[] getViewRootNames() {
synchronized (mLock) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 652fe24..da31078 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -228,14 +228,14 @@
if (mRealWm instanceof IWindowSession.Stub) {
mRealWm.grantInputChannel(displayId,
new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"),
- window, mHostInputToken, attrs.flags, attrs.privateFlags,
+ window.asBinder(), mHostInputToken, attrs.flags, attrs.privateFlags,
attrs.inputFeatures, attrs.type,
attrs.token, state.mInputTransferToken, attrs.getTitle().toString(),
outInputChannel);
} else {
- mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags,
- attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token,
- state.mInputTransferToken, attrs.getTitle().toString(),
+ mRealWm.grantInputChannel(displayId, sc, window.asBinder(), mHostInputToken,
+ attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type,
+ attrs.token, state.mInputTransferToken, attrs.getTitle().toString(),
outInputChannel);
}
state.mInputChannelToken =
@@ -245,8 +245,7 @@
}
}
- final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
- WindowManagerGlobal.ADD_FLAG_USE_BLAST;
+ final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
sendLayoutParamsToParent();
// Include whether the window is in touch mode.
@@ -593,7 +592,7 @@
List<Rect> unrestrictedRects) {}
@Override
- public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window,
+ public void grantInputChannel(int displayId, SurfaceControl surface, IBinder clientToken,
IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type,
IBinder windowToken, IBinder focusGrantToken, String inputHandleName,
InputChannel outInputChannel) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ab9566e..5296b99 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
}
flag {
+ name: "deduplicate_accessibility_warning_dialog"
+ namespace: "accessibility"
+ description: "Removes duplicate definition of the accessibility warning dialog."
+ bug: "303511250"
+}
+
+flag {
namespace: "accessibility"
name: "force_invert_color"
description: "Enable force force-dark for smart inversion and dark theme everywhere"
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index c4d43bc..eb2a101 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -86,6 +86,7 @@
* Granular level on which text should be operated.
*/
@IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD})
+ @Retention(RetentionPolicy.SOURCE)
@interface Granularity {}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index eeab005..589b7a3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1176,24 +1176,33 @@
mActive = interactive;
mFullscreenMode = fullscreen;
if (interactive) {
+ // Find the next view focus to start the input connection when the
+ // device was interactive.
final View rootView =
mCurRootView != null ? mCurRootView.getView() : null;
if (rootView == null) {
+ // No window focused or view was removed, ignore request.
return;
}
- // Find the next view focus to start the input connection when the
- // device was interactive.
final ViewRootImpl currentViewRootImpl = mCurRootView;
+ // Post this on UI thread as required for view focus code.
rootView.post(() -> {
synchronized (mH) {
if (mCurRootView != currentViewRootImpl) {
+ // Focused window changed since posting, ignore request.
return;
}
}
- final View focusedView = currentViewRootImpl.getView().findFocus();
+ final View curRootView = currentViewRootImpl.getView();
+ if (curRootView == null) {
+ // View was removed, ignore request.
+ return;
+ }
+ final View focusedView = curRootView.findFocus();
onViewFocusChangedInternal(focusedView, focusedView != null);
});
} else {
+ // Finish input connection when device becomes non-interactive.
finishInputLocked();
if (isImeSessionAvailableLocked()) {
mCurBindState.mImeSession.finishInput();
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index e44f436..4816f35 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -27,6 +27,9 @@
import android.os.Parcelable;
import android.os.RemoteCallback;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Information to be sent to SysUI about a back event.
*
@@ -85,6 +88,7 @@
TYPE_CROSS_TASK,
TYPE_CALLBACK
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface BackTargetType {
}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 0ce076b6..1bd921b 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* The window frame container class used by client side for layout.
* @hide
@@ -101,6 +103,29 @@
}
@Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ClientWindowFrames other = (ClientWindowFrames) o;
+ return frame.equals(other.frame)
+ && displayFrame.equals(other.displayFrame)
+ && parentFrame.equals(other.parentFrame)
+ && Objects.equals(attachedFrame, other.attachedFrame)
+ && isParentFrameClippedByDisplayCutout == other.isParentFrameClippedByDisplayCutout
+ && compatScale == other.compatScale;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(frame, displayFrame, parentFrame, attachedFrame,
+ isParentFrameClippedByDisplayCutout, compatScale);
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 7585826..cc875ad 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -37,7 +37,6 @@
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
@@ -426,7 +425,7 @@
// Setting as trusted overlay to let touches pass through. This is safe because this
// window is controlled by the system.
layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
- | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
+ | PRIVATE_FLAG_TRUSTED_OVERLAY;
layoutParams.token = token;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java
index f1c0d8d..b6c04d9 100644
--- a/core/java/android/window/SplashScreen.java
+++ b/core/java/android/window/SplashScreen.java
@@ -33,6 +33,8 @@
import android.util.Singleton;
import android.util.Slog;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -65,6 +67,7 @@
SPLASH_SCREEN_STYLE_SOLID_COLOR,
SPLASH_SCREEN_STYLE_ICON
})
+ @Retention(RetentionPolicy.SOURCE)
@interface SplashScreenStyle {}
/**
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 6497409..2b6913c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -34,6 +34,8 @@
*/
class AccessibilityServiceTarget extends AccessibilityTarget {
+ private final AccessibilityServiceInfo mAccessibilityServiceInfo;
+
AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType,
@NonNull AccessibilityServiceInfo serviceInfo) {
@@ -47,6 +49,7 @@
serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
convertToKey(convertToUserType(shortcutType)));
+ mAccessibilityServiceInfo = serviceInfo;
}
@Override
@@ -64,4 +67,8 @@
holder.mLabelView.setEnabled(enabled);
holder.mStatusView.setEnabled(enabled);
}
+
+ public AccessibilityServiceInfo getAccessibilityServiceInfo() {
+ return mAccessibilityServiceInfo;
+ }
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
new file mode 100644
index 0000000..0f8ced2
--- /dev/null
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java
@@ -0,0 +1,139 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.BidiFormatter;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Locale;
+
+/**
+ * Utility class for creating the dialog that asks the user for explicit permission
+ * before an accessibility service is enabled.
+ */
+public class AccessibilityServiceWarning {
+
+ /**
+ * Returns an {@link AlertDialog} to be shown to confirm that the user
+ * wants to enable an {@link android.accessibilityservice.AccessibilityService}.
+ */
+ public static AlertDialog createAccessibilityServiceWarningDialog(@NonNull Context context,
+ @NonNull AccessibilityServiceInfo info,
+ @NonNull View.OnClickListener allowListener,
+ @NonNull View.OnClickListener denyListener,
+ @NonNull View.OnClickListener uninstallListener) {
+ final AlertDialog ad = new AlertDialog.Builder(context)
+ .setView(createAccessibilityServiceWarningDialogContentView(
+ context, info, allowListener, denyListener, uninstallListener))
+ .setCancelable(true)
+ .create();
+ Window window = ad.getWindow();
+ WindowManager.LayoutParams params = window.getAttributes();
+ params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+ params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+ window.setAttributes(params);
+ return ad;
+ }
+
+ @VisibleForTesting
+ public static View createAccessibilityServiceWarningDialogContentView(Context context,
+ AccessibilityServiceInfo info,
+ View.OnClickListener allowListener,
+ View.OnClickListener denyListener,
+ View.OnClickListener uninstallListener) {
+ final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
+ final View content = inflater.inflate(R.layout.accessibility_service_warning, null);
+
+ final Drawable icon;
+ if (info.getResolveInfo().getIconResource() == 0) {
+ icon = context.getDrawable(R.drawable.ic_accessibility_generic);
+ } else {
+ icon = info.getResolveInfo().loadIcon(context.getPackageManager());
+ }
+ final ImageView permissionDialogIcon = content.findViewById(
+ R.id.accessibility_permissionDialog_icon);
+ permissionDialogIcon.setImageDrawable(icon);
+
+ final TextView permissionDialogTitle = content.findViewById(
+ R.id.accessibility_permissionDialog_title);
+ permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
+ getServiceName(context, info)));
+
+ final Button permissionAllowButton = content.findViewById(
+ R.id.accessibility_permission_enable_allow_button);
+ final Button permissionDenyButton = content.findViewById(
+ R.id.accessibility_permission_enable_deny_button);
+ permissionAllowButton.setOnClickListener(allowListener);
+ permissionAllowButton.setOnTouchListener(getTouchConsumingListener());
+ permissionDenyButton.setOnClickListener(denyListener);
+
+ final Button uninstallButton = content.findViewById(
+ R.id.accessibility_permission_enable_uninstall_button);
+ // Show an uninstall button to help users quickly remove non-preinstalled apps.
+ if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
+ uninstallButton.setVisibility(View.VISIBLE);
+ uninstallButton.setOnClickListener(uninstallListener);
+ }
+ return content;
+ }
+
+ @VisibleForTesting
+ @SuppressLint("ClickableViewAccessibility") // Touches are intentionally consumed
+ public static View.OnTouchListener getTouchConsumingListener() {
+ return (view, event) -> {
+ // Filter obscured touches by consuming them.
+ if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
+ || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ Toast.makeText(view.getContext(),
+ R.string.accessibility_dialog_touch_filtered_warning,
+ Toast.LENGTH_SHORT).show();
+ }
+ return true;
+ }
+ return false;
+ };
+ }
+
+ // Get the service name and bidi wrap it to protect from bidi side effects.
+ private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
+ final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
+ final CharSequence label =
+ info.getResolveInfo().loadLabel(context.getPackageManager());
+ return BidiFormatter.getInstance(locale).unicodeWrap(label);
+ }
+}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 987c14c..d4eccd4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -28,6 +28,7 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
@@ -56,7 +57,7 @@
"accessibility_shortcut_menu_mode";
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
private AlertDialog mMenuDialog;
- private AlertDialog mPermissionDialog;
+ private Dialog mPermissionDialog;
private ShortcutTargetAdapter mTargetAdapter;
@Override
@@ -123,7 +124,7 @@
if (target instanceof AccessibilityServiceTarget) {
showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
- mTargetAdapter);
+ position, mTargetAdapter);
return;
}
}
@@ -149,20 +150,43 @@
}
private void showPermissionDialogIfNeeded(Context context,
- AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) {
+ AccessibilityServiceTarget serviceTarget, int position,
+ ShortcutTargetAdapter targetAdapter) {
if (mPermissionDialog != null) {
return;
}
- mPermissionDialog = new AlertDialog.Builder(context)
- .setView(createEnableDialogContentView(context, serviceTarget,
- v -> {
- mPermissionDialog.dismiss();
- targetAdapter.notifyDataSetChanged();
- },
- v -> mPermissionDialog.dismiss()))
- .setOnDismissListener(dialog -> mPermissionDialog = null)
- .create();
+ if (Flags.deduplicateAccessibilityWarningDialog()) {
+ mPermissionDialog = AccessibilityServiceWarning
+ .createAccessibilityServiceWarningDialog(context,
+ serviceTarget.getAccessibilityServiceInfo(),
+ v -> {
+ serviceTarget.onCheckedChanged(true);
+ targetAdapter.notifyDataSetChanged();
+ mPermissionDialog.dismiss();
+ }, v -> {
+ serviceTarget.onCheckedChanged(false);
+ mPermissionDialog.dismiss();
+ },
+ v -> {
+ mTargets.remove(position);
+ context.getPackageManager().getPackageInstaller().uninstall(
+ serviceTarget.getComponentName().getPackageName(), null);
+ targetAdapter.notifyDataSetChanged();
+ mPermissionDialog.dismiss();
+ });
+ mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null);
+ } else {
+ mPermissionDialog = new AlertDialog.Builder(context)
+ .setView(createEnableDialogContentView(context, serviceTarget,
+ v -> {
+ mPermissionDialog.dismiss();
+ targetAdapter.notifyDataSetChanged();
+ },
+ v -> mPermissionDialog.dismiss()))
+ .setOnDismissListener(dialog -> mPermissionDialog = null)
+ .create();
+ }
mPermissionDialog.show();
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 0f85075..51a5ddf 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -296,6 +296,10 @@
}
}
+ /**
+ * @deprecated Use {@link AccessibilityServiceWarning}.
+ */
+ @Deprecated
static View createEnableDialogContentView(Context context,
AccessibilityServiceTarget target, View.OnClickListener allowListener,
View.OnClickListener denyListener) {
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index ea3c70f..c3d21a4 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -59,6 +59,7 @@
jfieldID layoutParamsType;
jfieldID dispatchingTimeoutMillis;
jfieldID frame;
+ jfieldID contentSize;
jfieldID surfaceInset;
jfieldID scaleFactor;
jfieldID touchableRegion;
@@ -281,6 +282,9 @@
ScopedLocalRef<jobject> rectObj(env, JNICommon::objFromRect(env, windowInfo.frame));
env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.frame, rectObj.get());
+ ScopedLocalRef<jobject> sizeObj(env, JNICommon::objFromSize(env, windowInfo.contentSize));
+ env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.contentSize, sizeObj.get());
+
env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.surfaceInset,
windowInfo.surfaceInset);
env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.scaleFactor,
@@ -393,6 +397,9 @@
GET_FIELD_ID(gInputWindowHandleClassInfo.frame, clazz, "frame", "Landroid/graphics/Rect;");
+ GET_FIELD_ID(gInputWindowHandleClassInfo.contentSize, clazz, "contentSize",
+ "Landroid/util/Size;");
+
GET_FIELD_ID(gInputWindowHandleClassInfo.surfaceInset, clazz,
"surfaceInset", "I");
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index b1dab85..8fa179b 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -574,7 +574,7 @@
if (result != NO_ERROR) {
return -1;
}
- return frameCount * channelCount * audio_bytes_per_sample(format);
+ return frameCount * audio_bytes_per_frame(channelCount, format);
}
static jboolean android_media_AudioRecord_setInputDevice(
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index 95bf49f..aebe7ea 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -16,15 +16,16 @@
#define LOG_TAG "PerfHint-jni"
-#include "jni.h"
-
+#include <android/performance_hint.h>
#include <dlfcn.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <utils/Log.h>
+
#include <vector>
#include "core_jni_helpers.h"
+#include "jni.h"
namespace android {
@@ -44,6 +45,11 @@
typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
+typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*);
+
+typedef AWorkDuration* (*AWD_create)();
+typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t);
+typedef void (*AWD_release)(AWorkDuration*);
bool gAPerformanceHintBindingInitialized = false;
APH_getManager gAPH_getManagerFn = nullptr;
@@ -56,6 +62,14 @@
APH_setThreads gAPH_setThreadsFn = nullptr;
APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
+APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr;
+
+AWD_create gAWD_createFn = nullptr;
+AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr;
+AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr;
+AWD_release gAWD_releaseFn = nullptr;
void ensureAPerformanceHintBindingInitialized() {
if (gAPerformanceHintBindingInitialized) return;
@@ -112,9 +126,46 @@
(APH_setPreferPowerEfficiency)dlsym(handle_,
"APerformanceHint_setPreferPowerEfficiency");
LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
- "Failed to find required symbol"
+ "Failed to find required symbol "
"APerformanceHint_setPreferPowerEfficiency!");
+ gAPH_reportActualWorkDuration2Fn =
+ (APH_reportActualWorkDuration2)dlsym(handle_,
+ "APerformanceHint_reportActualWorkDuration2");
+ LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr,
+ "Failed to find required symbol "
+ "APerformanceHint_reportActualWorkDuration2!");
+
+ gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create");
+ LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr,
+ "Failed to find required symbol AWorkDuration_create!");
+
+ gAWD_setWorkPeriodStartTimestampNanosFn =
+ (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos");
+ LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr,
+ "Failed to find required symbol "
+ "AWorkDuration_setWorkPeriodStartTimestampNanos!");
+
+ gAWD_setActualTotalDurationNanosFn =
+ (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos");
+ LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr,
+ "Failed to find required symbol "
+ "AWorkDuration_setActualTotalDurationNanos!");
+
+ gAWD_setActualCpuDurationNanosFn =
+ (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos");
+ LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr,
+ "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!");
+
+ gAWD_setActualGpuDurationNanosFn =
+ (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos");
+ LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr,
+ "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!");
+
+ gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release");
+ LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr,
+ "Failed to find required symbol AWorkDuration_release!");
+
gAPerformanceHintBindingInitialized = true;
}
@@ -238,6 +289,25 @@
enabled);
}
+static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+ jlong workPeriodStartTimestampNanos,
+ jlong actualTotalDurationNanos,
+ jlong actualCpuDurationNanos,
+ jlong actualGpuDurationNanos) {
+ ensureAPerformanceHintBindingInitialized();
+
+ AWorkDuration* workDuration = gAWD_createFn();
+ gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos);
+ gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos);
+ gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos);
+ gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos);
+
+ gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+ workDuration);
+
+ gAWD_releaseFn(workDuration);
+}
+
static const JNINativeMethod gPerformanceHintMethods[] = {
{"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
{"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -249,6 +319,7 @@
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
{"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
+ {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2},
};
int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/jni_common.cpp b/core/jni/jni_common.cpp
index b81c9b6..dd69b16 100644
--- a/core/jni/jni_common.cpp
+++ b/core/jni/jni_common.cpp
@@ -34,6 +34,11 @@
jfieldID top;
} gRectClassInfo;
+static struct {
+ jclass clazz;
+ jmethodID ctor;
+} gSizeClassInfo;
+
Rect JNICommon::rectFromObj(JNIEnv* env, jobject rectObj) {
int left = env->GetIntField(rectObj, gRectClassInfo.left);
int top = env->GetIntField(rectObj, gRectClassInfo.top);
@@ -47,6 +52,10 @@
rect.right, rect.bottom);
}
+jobject JNICommon::objFromSize(JNIEnv* env, Size size) {
+ return env->NewObject(gSizeClassInfo.clazz, gSizeClassInfo.ctor, size.width, size.height);
+}
+
int register_jni_common(JNIEnv* env) {
jclass rectClazz = FindClassOrDie(env, "android/graphics/Rect");
gRectClassInfo.clazz = MakeGlobalRefOrDie(env, rectClazz);
@@ -55,6 +64,11 @@
gRectClassInfo.left = GetFieldIDOrDie(env, rectClazz, "left", "I");
gRectClassInfo.right = GetFieldIDOrDie(env, rectClazz, "right", "I");
gRectClassInfo.top = GetFieldIDOrDie(env, rectClazz, "top", "I");
+
+ jclass sizeClazz = FindClassOrDie(env, "android/util/Size");
+ gSizeClassInfo.clazz = MakeGlobalRefOrDie(env, sizeClazz);
+ gSizeClassInfo.ctor = GetMethodIDOrDie(env, sizeClazz, "<init>", "(II)V");
+
return 0;
}
diff --git a/core/jni/jni_common.h b/core/jni/jni_common.h
index d670a7d..f1c6014 100644
--- a/core/jni/jni_common.h
+++ b/core/jni/jni_common.h
@@ -14,14 +14,17 @@
* limitations under the License.
*/
#include <jni.h>
+#include <ui/Size.h>
namespace android {
class Rect;
+using ui::Size;
class JNICommon {
public:
static Rect rectFromObj(JNIEnv* env, jobject rectObj);
static jobject objFromRect(JNIEnv* env, Rect rect);
+ static jobject objFromSize(JNIEnv* env, Size size);
};
} // namespace android
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6859f1f..76ae3e0 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8391,6 +8391,10 @@
android:exported="true">
</provider>
+ <meta-data
+ android:name="com.android.server.patch.25239169"
+ android:value="true" />
+
</application>
</manifest>
diff --git a/core/res/res/drawable/ic_accessibility_generic.xml b/core/res/res/drawable/ic_accessibility_generic.xml
new file mode 100644
index 0000000..68a89e6
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_generic.xml
@@ -0,0 +1,30 @@
+<!--
+ 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M5.6875,22.8235C4.9092,22.4776 4.8184,22.2615 2.8752,16.1257 1.8439,12.8691 1.0015,10.0882 1.0033,9.946 1.0137,9.1246 1.3166,8.8389 6.25,4.9976 9.2052,2.6966 11.2442,1.1943 11.5332,1.1049 11.8724,0.9999 12.1235,0.996 12.432,1.0907 12.9214,1.2408 22.3634,8.7104 22.6857,9.2024 23.1266,9.8752 23.0768,10.1907 22.0053,13.5155 19.0153,22.7935 19.1481,22.461 18.2853,22.8286 17.7053,23.0757 6.2446,23.0711 5.6875,22.8235Z"
+ android:strokeWidth="0.31999999"
+ android:fillColor="#ced6da"/>
+ <path
+ android:pathData="M10.0615,19.3507C10.028,19.2609 9.9864,17.362 9.9691,15.1308L9.9375,11.0741 8.5,10.853c-2.1981,-0.3381 -2.1924,-0.3355 -2.1619,-0.978 0.0141,-0.2963 0.074,-0.587 0.1331,-0.6462 0.06,-0.06 0.7667,0.0113 1.5994,0.1614 2.1217,0.3824 5.7371,0.3824 7.8588,0 0.8206,-0.1479 1.5349,-0.2259 1.5874,-0.1733 0.0525,0.0526 0.1334,0.3334 0.1799,0.624 0.078,0.4881 0.0598,0.5378 -0.2384,0.6512 -0.1776,0.0675 -1.0143,0.2259 -1.8593,0.352l-1.5364,0.2293 -0.0625,4.182 -0.0625,4.182l-0.625,0 -0.625,0l-0.0625,-1.875 -0.0625,-1.875l-0.5625,0L11.4375,15.6875l-0.0625,1.875 -0.0625,1.875 -0.595,0.0382c-0.4038,0.0259 -0.6146,-0.0143 -0.6559,-0.125zM11.3716,8.912c-0.4861,-0.3351 -0.6133,-0.5622 -0.6176,-1.1029 -0.0047,-0.6005 0.2255,-0.9684 0.739,-1.1811 0.8994,-0.3726 1.7571,0.2075 1.7571,1.1885 0,0.4533 -0.0659,0.5905 -0.4418,0.9206 -0.5007,0.4396 -0.9697,0.4967 -1.4366,0.1749z"
+ android:strokeWidth="0.31999999"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/core/res/res/layout/accessibility_service_warning.xml b/core/res/res/layout/accessibility_service_warning.xml
new file mode 100644
index 0000000..0381fac
--- /dev/null
+++ b/core/res/res/layout/accessibility_service_warning.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:textDirection="locale"
+ android:scrollbarStyle="outsideOverlay"
+ android:gravity="top">
+
+ <LinearLayout
+ android:accessibilityDataSensitive="yes"
+ style="@style/AccessibilityDialog">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp">
+
+ <ImageView
+ android:id="@+id/accessibility_permissionDialog_icon"
+ style="@style/AccessibilityDialogServiceIcon" />
+
+ <TextView
+ android:id="@+id/accessibility_permissionDialog_title"
+ style="@style/AccessibilityDialogTitle" />
+
+ <TextView
+ android:id="@+id/permissionDialog_description"
+ android:text="@string/accessibility_service_warning_description"
+ style="@style/AccessibilityDialogDescription" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="24dp" >
+
+ <ImageView
+ android:id="@+id/controlScreen_icon"
+ android:src="@drawable/ic_visibility"
+ style="@style/AccessibilityDialogIcon" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/controlScreen_title"
+ android:text="@string/accessibility_service_screen_control_title"
+ style="@style/AccessibilityDialogPermissionTitle" />
+
+ <TextView
+ android:id="@+id/controlScreen_description"
+ android:text="@string/accessibility_service_screen_control_description"
+ style="@style/AccessibilityDialogPermissionDescription" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="24dp" >
+
+ <ImageView
+ android:id="@+id/performAction_icon"
+ android:src="@drawable/ic_pan_tool"
+ style="@style/AccessibilityDialogIcon" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/performAction_title"
+ android:text="@string/accessibility_service_action_perform_title"
+ style="@style/AccessibilityDialogPermissionTitle" />
+
+ <TextView
+ android:id="@+id/performAction_description"
+ android:text="@string/accessibility_service_action_perform_description"
+ style="@style/AccessibilityDialogPermissionDescription" />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Buttons on bottom of dialog -->
+ <LinearLayout
+ style="@style/AccessibilityDialogButtonList">
+
+ <Space
+ style="@style/AccessibilityDialogButtonBarSpace"/>
+
+ <Button
+ android:id="@+id/accessibility_permission_enable_allow_button"
+ android:text="@string/accessibility_dialog_button_allow"
+ style="@style/AccessibilityDialogButton" />
+
+ <Button
+ android:id="@+id/accessibility_permission_enable_deny_button"
+ android:text="@string/accessibility_dialog_button_deny"
+ style="@style/AccessibilityDialogButton" />
+
+ <Button
+ android:id="@+id/accessibility_permission_enable_uninstall_button"
+ android:text="@string/accessibility_dialog_button_uninstall"
+ android:visibility="gone"
+ style="@style/AccessibilityDialogButton" />
+ </LinearLayout>
+ </LinearLayout>
+
+</ScrollView>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 30beee0..eddd81e 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -521,7 +521,7 @@
<color name="system_surface_dim_dark">#121316</color>
<color name="system_surface_variant_dark">#44474F</color>
<color name="system_on_surface_variant_dark">#C4C6D0</color>
- <color name="system_outline_dark">#72747D</color>
+ <color name="system_outline_dark">#8E9099</color>
<color name="system_outline_variant_dark">#444746</color>
<color name="system_error_dark">#FFB4A8</color>
<color name="system_on_error_dark">#690001</color>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cec83de..eed186a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4701,6 +4701,13 @@
<string name="accessibility_dialog_button_allow">Allow</string>
<!-- String for the deny button in accessibility permission dialog. [CHAR LIMIT=10] -->
<string name="accessibility_dialog_button_deny">Deny</string>
+ <!-- String for the uninstall button in accessibility permission dialog. -->
+ <string name="accessibility_dialog_button_uninstall">Uninstall</string>
+ <!-- Warning shown when user input has been blocked due to another app overlaying screen
+ content. Since we don't know what the app is showing on top of the input target, we
+ can't verify user consent. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_dialog_touch_filtered_warning">An app is obscuring the permission
+ request so your response cannot be verified.</string>
<!-- Title for accessibility select shortcut menu dialog. [CHAR LIMIT=100] -->
<string name="accessibility_select_shortcut_menu_title">Tap a feature to start using it:</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 13d04e5..619ec31 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1560,4 +1560,87 @@
<!-- The default style for input method switch dialog -->
<style name="InputMethodSwitchDialogStyle" parent="AlertDialog.DeviceDefault">
</style>
+
+ <style name="AccessibilityDialog">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:divider">@*android:drawable/list_divider_material</item>
+ <item name="android:showDividers">middle</item>
+ </style>
+
+ <style name="AccessibilityDialogServiceIcon">
+ <item name="android:layout_width">36dp</item>
+ <item name="android:layout_height">36dp</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ <item name="android:scaleType">fitCenter</item>
+ </style>
+
+ <style name="AccessibilityDialogIcon">
+ <item name="android:layout_width">18dp</item>
+ <item name="android:layout_height">18dp</item>
+ <item name="android:layout_marginEnd">12dp</item>
+ <item name="android:scaleType">fitCenter</item>
+ </style>
+
+ <style name="AccessibilityDialogTitle"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textSize">20sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ </style>
+
+ <style name="AccessibilityDialogDescription"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">32dp</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="AccessibilityDialogPermissionTitle"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
+ <style name="AccessibilityDialogPermissionDescription"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="AccessibilityDialogButtonBarSpace">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">0dp</item>
+ <item name="android:visibility">gone</item>
+ </style>
+
+ <style name="AccessibilityDialogButtonList">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:divider">@*android:drawable/list_divider_material</item>
+ <item name="android:showDividers">middle</item>
+ </style>
+
+ <style name="AccessibilityDialogButton"
+ parent="@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">56dp</item>
+ <item name="android:paddingEnd">8dp</item>
+ <item name="android:paddingStart">8dp</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 16ad5c9..f3aa936 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3622,11 +3622,14 @@
<java-symbol type="string" name="accessibility_uncheck_legacy_item_warning" />
<java-symbol type="layout" name="accessibility_enable_service_warning" />
+ <java-symbol type="layout" name="accessibility_service_warning" />
<java-symbol type="id" name="accessibility_permissionDialog_icon" />
<java-symbol type="id" name="accessibility_permissionDialog_title" />
<java-symbol type="id" name="accessibility_permission_enable_allow_button" />
<java-symbol type="id" name="accessibility_permission_enable_deny_button" />
+ <java-symbol type="id" name="accessibility_permission_enable_uninstall_button" />
<java-symbol type="string" name="accessibility_enable_service_title" />
+ <java-symbol type="string" name="accessibility_dialog_touch_filtered_warning" />
<java-symbol type="layout" name="accessibility_shortcut_chooser_item" />
<java-symbol type="id" name="accessibility_shortcut_target_checkbox" />
@@ -3655,6 +3658,7 @@
<java-symbol type="drawable" name="ic_accessibility_color_inversion" />
<java-symbol type="drawable" name="ic_accessibility_color_correction" />
+ <java-symbol type="drawable" name="ic_accessibility_generic" />
<java-symbol type="drawable" name="ic_accessibility_hearing_aid" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
<java-symbol type="drawable" name="ic_accessibility_reduce_bright_colors" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 20d8d91..62d58b6 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -165,6 +165,7 @@
<!-- AccessibilityShortcutChooserActivityTest permissions -->
<uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
+ <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
<application
android:theme="@style/Theme"
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 36e1223..4b02257 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -227,8 +227,7 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addCallback(ConfigurationChangeItem.obtain(
- new Configuration(newConfig), DEVICE_ID_INVALID));
+ transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -245,7 +244,7 @@
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
transaction.addCallback(ActivityConfigurationChangeItem.obtain(
- activity.getActivityToken(), new Configuration(newConfig)));
+ activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 4bbde0c..723c081 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
+import android.annotation.NonNull;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
@@ -80,74 +81,29 @@
@Test
public void testRecycleActivityConfigurationChangeItem() {
- ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(
- null /* activityToken */, Configuration.EMPTY);
- ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
- mActivityToken, config());
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain(
- mActivityToken, config());
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config()));
}
@Test
public void testRecycleActivityResultItem() {
- ActivityResultItem emptyItem = ActivityResultItem.obtain(
- null /* activityToken */, null /* resultInfoList */);
- ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- ActivityResultItem item2 = ActivityResultItem.obtain(mActivityToken, resultInfoList());
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList()));
}
@Test
public void testRecycleConfigurationChangeItem() {
- ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
- ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1));
}
@Test
public void testRecycleDestroyActivityItem() {
- DestroyActivityItem emptyItem = DestroyActivityItem.obtain(
- null /* activityToken */, false, 0);
- DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true, 117);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- DestroyActivityItem item2 = DestroyActivityItem.obtain(mActivityToken, true, 14);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
}
@Test
public void testRecycleLaunchActivityItem() {
final IBinder activityToken = new Binder();
final Intent intent = new Intent("action");
- int ident = 57;
+ final int ident = 57;
final ActivityInfo activityInfo = new ActivityInfo();
activityInfo.flags = 42;
activityInfo.setMaxAspectRatio(2.4f);
@@ -158,20 +114,19 @@
final Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
final String referrer = "referrer";
- int procState = 4;
+ final int procState = 4;
final Bundle bundle = new Bundle();
bundle.putString("key", "value");
final PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
final IBinder assistToken = new Binder();
final IBinder shareableActivityToken = new Binder();
- int deviceId = 3;
+ final int deviceId = 3;
+ final IBinder taskFragmentToken = new Binder();
- final Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
- .setActivityToken(activityToken)
- .setIntent(intent)
+ testRecycle(() -> new LaunchActivityItemBuilder(
+ activityToken, intent, activityInfo)
.setIdent(ident)
- .setInfo(activityInfo)
.setCurConfig(config())
.setOverrideConfig(overrideConfig)
.setReferrer(referrer)
@@ -183,154 +138,74 @@
.setIsForward(true)
.setAssistToken(assistToken)
.setShareableActivityToken(shareableActivityToken)
- .setTaskFragmentToken(new Binder())
+ .setTaskFragmentToken(taskFragmentToken)
.setDeviceId(deviceId)
- .build();
-
- LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
- LaunchActivityItem item = itemSupplier.get();
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- LaunchActivityItem item2 = itemSupplier.get();
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ .build());
}
@Test
public void testRecycleActivityRelaunchItem() {
- ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain(
- null /* activityToken */, null, null, 0, null, false);
- Configuration overrideConfig = new Configuration();
- overrideConfig.assetsSeq = 5;
- ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
- referrerIntentList(), 42, mergedConfig(), true);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
- referrerIntentList(), 42, mergedConfig(), true);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken,
+ resultInfoList(), referrerIntentList(), 42, mergedConfig(), true));
}
@Test
public void testRecycleMoveToDisplayItem() {
- MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(
- null /* activityToken */, 0, Configuration.EMPTY);
- MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4, config());
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- MoveToDisplayItem item2 = MoveToDisplayItem.obtain(mActivityToken, 3, config());
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config()));
}
@Test
public void testRecycleNewIntentItem() {
- NewIntentItem emptyItem = NewIntentItem.obtain(
- null /* activityToken */, null /* intents */, false /* resume */);
- NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- NewIntentItem item2 = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false));
}
@Test
public void testRecyclePauseActivityItemItem() {
- PauseActivityItem emptyItem = PauseActivityItem.obtain(
- null /* activityToken */, false, false, 0, false, false);
- PauseActivityItem item = PauseActivityItem.obtain(
- mActivityToken, true, true, 5, true, true);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- PauseActivityItem item2 = PauseActivityItem.obtain(
- mActivityToken, true, false, 5, true, true);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
}
@Test
public void testRecycleResumeActivityItem() {
- ResumeActivityItem emptyItem = ResumeActivityItem.obtain(
- null /* activityToken */, false, false);
- ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 3, true, false);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- ResumeActivityItem item2 = ResumeActivityItem.obtain(mActivityToken, 2, true, false);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false));
}
@Test
public void testRecycleStartActivityItem() {
- StartActivityItem emptyItem = StartActivityItem.obtain(
- null /* activityToken */, null /* activityOptions */);
- StartActivityItem item = StartActivityItem.obtain(mActivityToken,
- ActivityOptions.makeBasic());
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- StartActivityItem item2 = StartActivityItem.obtain(mActivityToken,
- ActivityOptions.makeBasic().setLaunchDisplayId(10));
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic()));
}
@Test
public void testRecycleStopItem() {
- StopActivityItem emptyItem = StopActivityItem.obtain(null /* activityToken */, 0);
- StopActivityItem item = StopActivityItem.obtain(mActivityToken, 4);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- StopActivityItem item2 = StopActivityItem.obtain(mActivityToken, 3);
- assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+ testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
}
@Test
public void testRecycleClientTransaction() {
- ClientTransaction emptyItem = ClientTransaction.obtain(null);
- ClientTransaction item = ClientTransaction.obtain(mApplicationThread);
- assertNotSame(item, emptyItem);
- assertNotEquals(item, emptyItem);
+ testRecycle(() -> ClientTransaction.obtain(mApplicationThread));
+ }
+ private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) {
+ // Reuse the same object after recycle.
+ final ObjectPoolItem item = obtain.get();
item.recycle();
- assertEquals(item, emptyItem);
+ final ObjectPoolItem item2 = obtain.get();
- ClientTransaction item2 = ClientTransaction.obtain(mApplicationThread);
assertSame(item, item2);
- assertNotEquals(item2, emptyItem);
+
+ // Create new object when the pool is empty.
+ final ObjectPoolItem item3 = obtain.get();
+
+ assertNotSame(item, item3);
+ assertEquals(item, item3);
+
+ // Reset fields after recycle.
+ item.recycle();
+
+ assertNotEquals(item, item3);
+
+ // Recycled objects are equal.
+ item3.recycle();
+
+ assertEquals(item, item3);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 5a88bad..c0e2a49 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -18,6 +18,8 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
@@ -92,17 +94,18 @@
}
static class LaunchActivityItemBuilder {
- @Nullable
- private IBinder mActivityToken;
- @Nullable
- private Intent mIntent;
+ @NonNull
+ private final IBinder mActivityToken;
+ @NonNull
+ private final Intent mIntent;
+ @NonNull
+ private final ActivityInfo mInfo;
+ @NonNull
+ private final Configuration mCurConfig = new Configuration();
+ @NonNull
+ private final Configuration mOverrideConfig = new Configuration();
+
private int mIdent;
- @Nullable
- private ActivityInfo mInfo;
- @Nullable
- private Configuration mCurConfig;
- @Nullable
- private Configuration mOverrideConfig;
private int mDeviceId;
@Nullable
private String mReferrer;
@@ -130,16 +133,11 @@
@Nullable
private IBinder mTaskFragmentToken;
- @NonNull
- LaunchActivityItemBuilder setActivityToken(@Nullable IBinder activityToken) {
- mActivityToken = activityToken;
- return this;
- }
-
- @NonNull
- LaunchActivityItemBuilder setIntent(@Nullable Intent intent) {
- mIntent = intent;
- return this;
+ LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent,
+ @NonNull ActivityInfo info) {
+ mActivityToken = requireNonNull(activityToken);
+ mIntent = requireNonNull(intent);
+ mInfo = requireNonNull(info);
}
@NonNull
@@ -149,20 +147,14 @@
}
@NonNull
- LaunchActivityItemBuilder setInfo(@Nullable ActivityInfo info) {
- mInfo = info;
+ LaunchActivityItemBuilder setCurConfig(@NonNull Configuration curConfig) {
+ mCurConfig.setTo(curConfig);
return this;
}
@NonNull
- LaunchActivityItemBuilder setCurConfig(@Nullable Configuration curConfig) {
- mCurConfig = curConfig;
- return this;
- }
-
- @NonNull
- LaunchActivityItemBuilder setOverrideConfig(@Nullable Configuration overrideConfig) {
- mOverrideConfig = overrideConfig;
+ LaunchActivityItemBuilder setOverrideConfig(@NonNull Configuration overrideConfig) {
+ mOverrideConfig.setTo(overrideConfig);
return this;
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index f2b0f2e..443dcb4 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -44,6 +44,8 @@
import android.app.ClientTransactionHandler;
import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -290,7 +292,7 @@
// A previous queued launch transaction runs on main thread (execute).
final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+ spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
launchTransaction.addCallback(launchItem);
mExecutor.execute(launchTransaction);
@@ -322,7 +324,7 @@
// A previous queued launch transaction runs on main thread (execute).
final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+ spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
launchTransaction.addTransactionItem(launchItem);
mExecutor.execute(launchTransaction);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 4aa62c5..07921bf 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -173,11 +173,9 @@
final PersistableBundle persistableBundle = new PersistableBundle();
persistableBundle.putInt("k", 4);
- final LaunchActivityItem item = new LaunchActivityItemBuilder()
- .setActivityToken(activityToken)
- .setIntent(intent)
+ final LaunchActivityItem item = new LaunchActivityItemBuilder(
+ activityToken, intent, activityInfo)
.setIdent(ident)
- .setInfo(activityInfo)
.setCurConfig(config())
.setOverrideConfig(overrideConfig)
.setReferrer(referrer)
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
index c00eb91..4d45daf 100644
--- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java
@@ -52,16 +52,18 @@
private PendingTransactionActions mPendingActions;
@Mock
private IWindow mWindow;
- @Mock
- private ClientWindowFrames mFrames;
- @Mock
- private MergedConfiguration mConfiguration;
- @Mock
+
private InsetsState mInsetsState;
+ private ClientWindowFrames mFrames;
+ private MergedConfiguration mConfiguration;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+
+ mInsetsState = new InsetsState();
+ mFrames = new ClientWindowFrames();
+ mConfiguration = new MergedConfiguration();
}
@Test
diff --git a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
deleted file mode 100644
index e19c4b1..0000000
--- a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link android.content.pm.LauncherActivityInfo}
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class LauncherActivityInfoTest {
-
- @Test
- public void testTrimStart() {
- // Invisible case
- assertThat(LauncherActivityInfo.trimStart("\u0009").toString()).isEmpty();
- // It is not supported in the system font
- assertThat(LauncherActivityInfo.trimStart("\u0FE1").toString()).isEmpty();
- // Surrogates case
- assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36").toString())
- .isEqualTo("\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trimStart("\u0009\u0FE1\uD83E\uDD36A").toString())
- .isEqualTo("\uD83E\uDD36A");
- assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
- assertThat(LauncherActivityInfo.trimStart("A\uD83E\uDD36\u0009\u0FE1A").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
- assertThat(LauncherActivityInfo.trimStart(
- "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trimStart(
- "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\uD83E\uDD36A\u0009\u0FE1");
- }
-
- @Test
- public void testTrimEnd() {
- // Invisible case
- assertThat(LauncherActivityInfo.trimEnd("\u0009").toString()).isEmpty();
- // It is not supported in the system font
- assertThat(LauncherActivityInfo.trimEnd("\u0FE1").toString()).isEmpty();
- // Surrogates case
- assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36").toString())
- .isEqualTo("\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trimEnd("\u0009\u0FE1\uD83E\uDD36A").toString())
- .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
- assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\uD83E\uDD36A");
- assertThat(LauncherActivityInfo.trimEnd("A\uD83E\uDD36\u0009\u0FE1A").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
- assertThat(LauncherActivityInfo.trimEnd(
- "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trimEnd(
- "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\u0009\u0FE1\uD83E\uDD36A");
- }
-
- @Test
- public void testTrim() {
- // Invisible case
- assertThat(LauncherActivityInfo.trim("\u0009").toString()).isEmpty();
- // It is not supported in the system font
- assertThat(LauncherActivityInfo.trim("\u0FE1").toString()).isEmpty();
- // Surrogates case
- assertThat(LauncherActivityInfo.trim("\uD83E\uDD36").toString())
- .isEqualTo("\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trim("\u0009\u0FE1\uD83E\uDD36A").toString())
- .isEqualTo("\uD83E\uDD36A");
- assertThat(LauncherActivityInfo.trim("\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\uD83E\uDD36A");
- assertThat(LauncherActivityInfo.trim("A\uD83E\uDD36\u0009\u0FE1A").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A");
- assertThat(LauncherActivityInfo.trim(
- "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString())
- .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36");
- assertThat(LauncherActivityInfo.trim(
- "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString())
- .isEqualTo("\uD83E\uDD36A");
- }
-}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 20ba427..9b4dec4 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -182,4 +182,42 @@
s.setPreferPowerEfficiency(true);
s.setPreferPowerEfficiency(true);
}
+
+ @Test
+ public void testReportActualWorkDurationWithWorkDurationClass() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.updateTargetWorkDuration(16);
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6));
+ s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20));
+ s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6));
+ }
+
+ @Test
+ public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.updateTargetWorkDuration(16);
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6));
+ });
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1));
+ });
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index dd116b5..088b57f 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -44,14 +44,19 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.AlertDialog;
import android.app.KeyguardManager;
+import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
+import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -62,6 +67,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
@@ -90,12 +96,17 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutChooserActivityTest {
private static final String ONE_HANDED_MODE = "One-Handed mode";
+ private static final String ALLOW_LABEL = "Allow";
private static final String DENY_LABEL = "Deny";
+ private static final String UNINSTALL_LABEL = "Uninstall";
private static final String EDIT_LABEL = "Edit shortcuts";
private static final String LIST_TITLE_LABEL = "Choose features to use";
private static final String TEST_LABEL = "TEST_LABEL";
- private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
+ private static final String TEST_PACKAGE = "TEST_LABEL";
+ private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(TEST_PACKAGE,
+ "class");
private static final long UI_TIMEOUT_MS = 1000;
+ private UiAutomation mUiAutomation;
private UiDevice mDevice;
private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
private TestAccessibilityShortcutChooserActivity mActivity;
@@ -117,6 +128,10 @@
private IAccessibilityManager mAccessibilityManagerService;
@Mock
private KeyguardManager mKeyguardManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageInstaller mPackageInstaller;
@Before
public void setUp() throws Exception {
@@ -125,6 +140,7 @@
assumeFalse("AccessibilityShortcutChooserActivity not supported on watch",
pm.hasSystemFeature(PackageManager.FEATURE_WATCH));
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
mDevice.wakeUp();
when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
@@ -134,12 +150,15 @@
when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
anyInt())).thenReturn(new ParceledListSlice<>(
- Collections.singletonList(mAccessibilityServiceInfo)));
+ Collections.singletonList(mAccessibilityServiceInfo)));
when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
anyString(), anyInt(), anyInt())).thenReturn(true);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller);
+
TestAccessibilityShortcutChooserActivity.setupForTesting(
- mAccessibilityManagerService, mKeyguardManager);
+ mAccessibilityManagerService, mKeyguardManager,
+ mPackageManager);
}
@After
@@ -150,18 +169,12 @@
}
@Test
- public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() {
+ @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() {
launchActivity();
openShortcutsList();
- // Performing the double-click is flaky so retry if needed.
- for (int attempt = 1; attempt <= 2; attempt++) {
- onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
- if (mDevice.wait(Until.hasObject(By.text(DENY_LABEL)), UI_TIMEOUT_MS)) {
- break;
- }
- }
-
+ mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
onView(withText(DENY_LABEL)).perform(scrollTo(), click());
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -170,6 +183,50 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_permissionDialog_allow_rowChecked() {
+ launchActivity();
+ openShortcutsList();
+
+ mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+ clickSystemDialogButton(ALLOW_LABEL);
+
+ assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+ UI_TIMEOUT_MS)).isTrue();
+ assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_permissionDialog_deny_rowNotChecked() {
+ launchActivity();
+ openShortcutsList();
+
+ mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+ clickSystemDialogButton(DENY_LABEL);
+
+ assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+ UI_TIMEOUT_MS)).isTrue();
+ assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+ public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() {
+ launchActivity();
+ openShortcutsList();
+
+ mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS);
+ clickSystemDialogButton(UNINSTALL_LABEL);
+
+ verify(mPackageInstaller).uninstall(eq(TEST_PACKAGE), any());
+ assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)),
+ UI_TIMEOUT_MS)).isTrue();
+ assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(TEST_LABEL)),
+ UI_TIMEOUT_MS)).isFalse();
+ }
+
+ @Test
public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent()
throws Exception {
when(mAccessibilityManagerService.isAccessibilityTargetAllowed(
@@ -239,6 +296,18 @@
mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS);
}
+ private void clickSystemDialogButton(String dialogButtonText) {
+ // Use UiAutomation to find the button because UiDevice struggles to find
+ // a UI element in a system dialog.
+ final AccessibilityNodeInfo button =
+ mUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByText(dialogButtonText).stream()
+ .filter(AccessibilityNodeInfo::isClickable).findFirst().get();
+ final Rect bounds = new Rect();
+ button.getBoundsInScreen(bounds);
+ mDevice.click(bounds.centerX(), bounds.centerY());
+ }
+
/**
* Used for testing.
*/
@@ -246,12 +315,30 @@
AccessibilityShortcutChooserActivity {
private static IAccessibilityManager sAccessibilityManagerService;
private static KeyguardManager sKeyguardManager;
+ private static PackageManager sPackageManager;
public static void setupForTesting(
IAccessibilityManager accessibilityManagerService,
- KeyguardManager keyguardManager) {
+ KeyguardManager keyguardManager,
+ PackageManager packageManager) {
sAccessibilityManagerService = accessibilityManagerService;
sKeyguardManager = keyguardManager;
+ sPackageManager = packageManager;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (Flags.deduplicateAccessibilityWarningDialog()) {
+ // Setting the Theme is necessary here for the dialog to use the proper style
+ // resources as designated in its layout XML.
+ setTheme(R.style.Theme_DeviceDefault_DayNight);
+ }
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return sPackageManager;
}
@Override
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
new file mode 100644
index 0000000..b76dd51
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.internal.accessibility.dialog;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.TestUtils;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Unit Tests for
+ * {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning}
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@RequiresFlagsEnabled(
+ android.view.accessibility.Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG)
+public class AccessibilityServiceWarningTest {
+ private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService";
+ private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary";
+ private static final String A11Y_SERVICE_COMPONENT_NAME =
+ "fake.package/test.a11yservice.name";
+
+ private Context mContext;
+ private AccessibilityServiceInfo mAccessibilityServiceInfo;
+ private AtomicBoolean mAllowListener;
+ private AtomicBoolean mDenyListener;
+ private AtomicBoolean mUninstallListener;
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mAccessibilityServiceInfo = TestUtils.createFakeServiceInfo(
+ A11Y_SERVICE_PACKAGE_LABEL,
+ A11Y_SERVICE_SUMMARY,
+ A11Y_SERVICE_COMPONENT_NAME,
+ /* isAlwaysOnService*/ false);
+ mAllowListener = new AtomicBoolean(false);
+ mDenyListener = new AtomicBoolean(false);
+ mUninstallListener = new AtomicBoolean(false);
+ }
+
+ @Test
+ public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams() {
+ final AlertDialog dialog =
+ AccessibilityServiceWarning.createAccessibilityServiceWarningDialog(
+ mContext,
+ mAccessibilityServiceInfo,
+ null, null, null);
+ final Window dialogWindow = dialog.getWindow();
+ assertThat(dialogWindow).isNotNull();
+
+ expect.that(dialogWindow.getAttributes().privateFlags
+ & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo(
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG);
+ }
+
+ @Test
+ public void createAccessibilityServiceWarningDialog_hasExpectedServiceName() {
+ final TextView title = createDialogContentView().findViewById(
+ R.id.accessibility_permissionDialog_title);
+ assertThat(title).isNotNull();
+
+ assertThat(title.getText().toString()).contains(A11Y_SERVICE_PACKAGE_LABEL);
+ }
+
+ @Test
+ public void createAccessibilityServiceWarningDialog_clickAllow() {
+ final View allowButton = createDialogContentView().findViewById(
+ R.id.accessibility_permission_enable_allow_button);
+ assertThat(allowButton).isNotNull();
+
+ allowButton.performClick();
+
+ expect.that(mAllowListener.get()).isTrue();
+ expect.that(mDenyListener.get()).isFalse();
+ expect.that(mUninstallListener.get()).isFalse();
+ }
+
+ @Test
+ public void createAccessibilityServiceWarningDialog_clickDeny() {
+ final View denyButton = createDialogContentView().findViewById(
+ R.id.accessibility_permission_enable_deny_button);
+ assertThat(denyButton).isNotNull();
+
+ denyButton.performClick();
+
+ expect.that(mAllowListener.get()).isFalse();
+ expect.that(mDenyListener.get()).isTrue();
+ expect.that(mUninstallListener.get()).isFalse();
+ }
+
+ @Test
+ public void createAccessibilityServiceWarningDialog_clickUninstall() {
+ final View uninstallButton = createDialogContentView().findViewById(
+ R.id.accessibility_permission_enable_uninstall_button);
+ assertThat(uninstallButton).isNotNull();
+
+ uninstallButton.performClick();
+
+ expect.that(mAllowListener.get()).isFalse();
+ expect.that(mDenyListener.get()).isFalse();
+ expect.that(mUninstallListener.get()).isTrue();
+ }
+
+ @Test
+ public void getTouchConsumingListener() {
+ final View allowButton = createDialogContentView().findViewById(
+ R.id.accessibility_permission_enable_allow_button);
+ assertThat(allowButton).isNotNull();
+ final View.OnTouchListener listener =
+ AccessibilityServiceWarning.getTouchConsumingListener();
+
+ expect.that(listener.onTouch(allowButton, createMotionEvent(0))).isFalse();
+ expect.that(listener.onTouch(allowButton,
+ createMotionEvent(MotionEvent.FLAG_WINDOW_IS_OBSCURED))).isTrue();
+ expect.that(listener.onTouch(allowButton,
+ createMotionEvent(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED))).isTrue();
+ }
+
+ private View createDialogContentView() {
+ return AccessibilityServiceWarning.createAccessibilityServiceWarningDialogContentView(
+ mContext,
+ mAccessibilityServiceInfo,
+ (v) -> mAllowListener.set(true),
+ (v) -> mDenyListener.set(true),
+ (v) -> mUninstallListener.set(true));
+ }
+
+ private MotionEvent createMotionEvent(int flags) {
+ MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[]{
+ new MotionEvent.PointerProperties()
+ };
+ MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[]{
+ new MotionEvent.PointerCoords()
+ };
+ return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1, props, coords,
+ 0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN, flags);
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index dc2b056..b065611 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -415,12 +415,6 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
- "-1717147904": {
- "message": "Current focused window is embeddedWindow. Dispatch KEYCODE_BACK.",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"-1710206702": {
"message": "Display id=%d is frozen while keyguard locked, return %d",
"level": "VERBOSE",
@@ -1213,12 +1207,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "-997565097": {
- "message": "Focused window found using getFocusedWindowToken",
- "level": "DEBUG",
- "group": "WM_DEBUG_BACK_PREVIEW",
- "at": "com\/android\/server\/wm\/BackNavigationController.java"
- },
"-993378225": {
"message": "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s",
"level": "VERBOSE",
@@ -2233,6 +2221,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "-98422345": {
+ "message": "Focus window is closing.",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"-91393839": {
"message": "Set animatingExit: reason=remove\/applyAnimation win=%s",
"level": "VERBOSE",
@@ -2731,12 +2725,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "309039362": {
- "message": "SURFACE MATRIX [%f,%f,%f,%f]: %s",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
- },
"312030608": {
"message": "New topFocusedDisplayId=%d",
"level": "DEBUG",
@@ -3091,12 +3079,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "633654009": {
- "message": "SURFACE POS (setPositionInTransaction) @ (%f,%f): %s",
- "level": "INFO",
- "group": "WM_SHOW_TRANSACTIONS",
- "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
- },
"638429464": {
"message": "\tRemove container=%s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index 66fabec..a4bce9e 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -23,6 +23,8 @@
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.Buffer;
import java.nio.ShortBuffer;
@@ -43,6 +45,7 @@
* Determines how the mesh is represented and will be drawn.
*/
@IntDef({TRIANGLES, TRIANGLE_STRIP})
+ @Retention(RetentionPolicy.SOURCE)
private @interface Mode {}
/**
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index 5509f00..45e29a8 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -62,6 +62,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
@@ -116,6 +118,7 @@
*/
@IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP,
TYPE_URI_ADAPTIVE_BITMAP})
+ @Retention(RetentionPolicy.SOURCE)
public @interface IconType {
}
diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
index 5c8c84c..ed00a87 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
@@ -21,8 +21,8 @@
android:layout_width="wrap_content"
android:paddingTop="48dp"
android:paddingBottom="48dp"
- android:paddingEnd="@dimen/bubble_user_education_padding_end"
- android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+ android:paddingHorizontal="@dimen/bubble_user_education_padding_horizontal"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index b28f58f..4f6bdfd 100644
--- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -23,8 +23,8 @@
android:clickable="true"
android:paddingTop="28dp"
android:paddingBottom="16dp"
- android:paddingEnd="@dimen/bubble_user_education_padding_end"
- android:layout_marginEnd="@dimen/bubble_user_education_margin_end"
+ android:paddingEnd="@dimen/bubble_user_education_padding_horizontal"
+ android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal"
android:orientation="vertical"
android:background="@drawable/bubble_stack_user_education_bg"
>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index de9d2a2..f20d44d 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -224,9 +224,9 @@
<dimen name="bubbles_user_education_width">480dp</dimen>
<!-- Margin applied to the end of the user education views (really only matters for phone
since the width is match parent). -->
- <dimen name="bubble_user_education_margin_end">24dp</dimen>
+ <dimen name="bubble_user_education_margin_horizontal">24dp</dimen>
<!-- Padding applied to the end of the user education view. -->
- <dimen name="bubble_user_education_padding_end">58dp</dimen>
+ <dimen name="bubble_user_education_padding_horizontal">58dp</dimen>
<!-- Padding between the bubble and the user education text. -->
<dimen name="bubble_user_education_stack_padding">16dp</dimen>
<!-- Max width for the bubble popup view. -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 144c456..09ae84a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -46,6 +46,12 @@
? "BubblePositioner"
: BubbleDebugConfig.TAG_BUBBLES;
+ /** The screen edge the bubble stack is pinned to */
+ public enum StackPinnedEdge {
+ LEFT,
+ RIGHT
+ }
+
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
@@ -694,6 +700,15 @@
final boolean startOnLeft = isAppBubble
? layoutDirection == LAYOUT_DIRECTION_RTL
: layoutDirection != LAYOUT_DIRECTION_RTL;
+ return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT);
+ }
+
+ /**
+ * The stack position to use if user education is being shown.
+ *
+ * @param stackPinnedEdge the screen edge the stack is pinned to.
+ */
+ public PointF getStartPosition(StackPinnedEdge stackPinnedEdge) {
final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
1 /* default starts with 1 bubble */);
if (isLargeScreen()) {
@@ -702,7 +717,7 @@
final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
final float offset = desiredY / mScreenRect.height();
return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
+ stackPinnedEdge == StackPinnedEdge.LEFT,
offset)
.getAbsolutePositionInRegion(allowableStackPositionRegion);
} else {
@@ -710,7 +725,7 @@
R.dimen.bubble_stack_starting_offset_y);
// TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
return new BubbleStackView.RelativeStackPosition(
- startOnLeft,
+ stackPinnedEdge == StackPinnedEdge.LEFT,
startingVerticalOffset / mPositionRect.height())
.getAbsolutePositionInRegion(allowableStackPositionRegion);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 87461dc..2cee675 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -25,6 +25,8 @@
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT;
+import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
@@ -1296,6 +1298,9 @@
return shouldShow;
}
+ /**
+ * Show manage education if should show and was not showing before.
+ */
private void maybeShowManageEdu() {
if (!shouldShowManageEdu()) {
return;
@@ -1304,7 +1309,16 @@
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
}
- mManageEduView.show(mExpandedBubble.getExpandedView());
+ showManageEdu();
+ }
+
+ /**
+ * Show manage education if was not showing before.
+ */
+ private void showManageEdu() {
+ if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return;
+ mManageEduView.show(mExpandedBubble.getExpandedView(),
+ mStackAnimationController.isStackOnLeftSide());
}
@VisibleForTesting
@@ -1350,10 +1364,21 @@
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
}
+ return showStackEdu();
+ }
+
+ /**
+ * @return true if education view for the collapsed stack was not showing before.
+ */
+ private boolean showStackEdu() {
+ // Stack appears on top of the education views
mBubbleContainer.bringToFront();
// Ensure the stack is in the correct spot
- mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
- return mStackEduView.show(mPositioner.getDefaultStartPosition());
+ PointF position = mPositioner.getStartPosition(
+ mStackAnimationController.isStackOnLeftSide() ? LEFT : RIGHT);
+ // Animate stack to the position
+ mStackAnimationController.springStackAfterFling(position.x, position.y);
+ return mStackEduView.show(position);
}
@VisibleForTesting
@@ -1367,16 +1392,13 @@
removeView(mStackEduView);
mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController);
addView(mStackEduView);
- mBubbleContainer.bringToFront(); // Stack appears on top of the stack education
- // Ensure the stack is in the correct spot
- mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition());
- mStackEduView.show(mPositioner.getDefaultStartPosition());
+ showStackEdu();
}
if (isManageEduVisible()) {
removeView(mManageEduView);
mManageEduView = new ManageEducationView(mContext, mPositioner);
addView(mManageEduView);
- mManageEduView.show(mExpandedBubble.getExpandedView());
+ showManageEdu();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 1b41f79..61e17c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -19,6 +19,7 @@
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -32,10 +33,10 @@
* User education view to highlight the manage button that allows a user to configure the settings
* for the bubble. Shown only the first time a user expands a bubble.
*/
-class ManageEducationView constructor(context: Context, positioner: BubblePositioner)
- : LinearLayout(context) {
+class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) {
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
+ private val TAG =
+ if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION: Long = 200
@@ -62,7 +63,7 @@
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
+ setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
}
override fun onFinishInflate() {
@@ -71,8 +72,10 @@
}
private fun setButtonColor() {
- val typedArray = mContext.obtainStyledAttributes(intArrayOf(
- com.android.internal.R.attr.colorAccentPrimary))
+ val typedArray =
+ mContext.obtainStyledAttributes(
+ intArrayOf(com.android.internal.R.attr.colorAccentPrimary)
+ )
val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT)
typedArray.recycle()
@@ -81,11 +84,11 @@
gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor))
}
- private fun setDrawableDirection() {
+ private fun setDrawableDirection(isOnLeft: Boolean) {
manageView.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
- R.drawable.bubble_stack_user_education_bg_rtl
- else R.drawable.bubble_stack_user_education_bg)
+ if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+ else R.drawable.bubble_stack_user_education_bg_rtl
+ )
}
/**
@@ -93,48 +96,31 @@
* bubble stack is expanded for the first time.
*
* @param expandedView the expandedView the user education is shown on top of.
+ * @param isStackOnLeft the bubble stack position on the screen
*/
- fun show(expandedView: BubbleExpandedView) {
+ fun show(expandedView: BubbleExpandedView, isStackOnLeft: Boolean) {
setButtonColor()
if (visibility == VISIBLE) return
bubbleExpandedView = expandedView
expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect))
- layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
- context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
- else ViewGroup.LayoutParams.MATCH_PARENT
-
alpha = 0f
visibility = View.VISIBLE
expandedView.getManageButtonBoundsOnScreen(realManageButtonRect)
- val isRTL = mContext.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
- if (isRTL) {
- val rightPadding = positioner.screenRect.right - realManageButtonRect.right -
- expandedView.manageButtonMargin
- manageView.setPadding(manageView.paddingLeft, manageView.paddingTop,
- rightPadding, manageView.paddingBottom)
- } else {
- manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin,
- manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom)
- }
+ layoutManageView(realManageButtonRect, expandedView.manageButtonMargin, isStackOnLeft)
+
post {
- manageButton
- .setOnClickListener {
- hide()
- expandedView.requireViewById<View>(R.id.manage_button).performClick()
- }
+ manageButton.setOnClickListener {
+ hide()
+ expandedView.requireViewById<View>(R.id.manage_button).performClick()
+ }
gotItButton.setOnClickListener { hide() }
setOnClickListener { hide() }
val offsetViewBounds = Rect()
manageButton.getDrawingRect(offsetViewBounds)
manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds)
- if (isRTL && (positioner.isLargeScreen || positioner.isLandscape)) {
- translationX = (positioner.screenRect.right - width).toFloat()
- } else {
- translationX = 0f
- }
translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat()
bringToFront()
animate()
@@ -145,6 +131,79 @@
setShouldShow(false)
}
+ /**
+ * On tablet the user education is aligned to the left or to right side depending on where the
+ * stack is positioned when collapsed. On phone the user education follows the layout direction.
+ *
+ * @param manageButtonRect the manage button rect on the screen
+ * @param manageButtonMargin the manage button margin
+ * @param isStackOnLeft the bubble stack position on the screen
+ */
+ private fun layoutManageView(
+ manageButtonRect: Rect,
+ manageButtonMargin: Int,
+ isStackOnLeft: Boolean
+ ) {
+ val isLTR = resources.configuration.layoutDirection == LAYOUT_DIRECTION_LTR
+ val isPinnedLeft = if (positioner.isLargeScreen) isStackOnLeft else isLTR
+ val paddingHorizontal =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_padding_horizontal)
+
+ // The user education view background image direction
+ setDrawableDirection(isPinnedLeft)
+
+ // The user education view layout gravity
+ gravity = if (isPinnedLeft) Gravity.LEFT else Gravity.RIGHT
+
+ // The user education view width
+ manageView.layoutParams.width =
+ when {
+ // Left-to-Right direction and the education is on the right side
+ isLTR && !isPinnedLeft ->
+ positioner.screenRect.right -
+ (manageButtonRect.left - manageButtonMargin - paddingHorizontal)
+ // Right-to-Left direction and the education is on the left side
+ !isLTR && isPinnedLeft ->
+ manageButtonRect.right + manageButtonMargin + paddingHorizontal
+ // Large screen and the education position matches the layout direction
+ positioner.isLargeScreen -> ViewGroup.LayoutParams.WRAP_CONTENT
+ // Small screen, landscape orientation
+ positioner.isLandscape ->
+ resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+ // Otherwise
+ else -> ViewGroup.LayoutParams.MATCH_PARENT
+ }
+
+ // The user education view margin on the opposite side of where it's pinned
+ (manageView.layoutParams as MarginLayoutParams).apply {
+ val edgeMargin =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+ leftMargin = if (isPinnedLeft) 0 else edgeMargin
+ rightMargin = if (isPinnedLeft) edgeMargin else 0
+ }
+
+ // The user education view padding
+ manageView.apply {
+ val paddingLeft =
+ if (isLTR && isPinnedLeft) {
+ // Offset on the left to align with the manage button
+ manageButtonRect.left - manageButtonMargin
+ } else {
+ // Use default padding
+ paddingHorizontal
+ }
+ val paddingRight =
+ if (!isLTR && !isPinnedLeft) {
+ // Offset on the right to align with the manage button
+ positioner.screenRect.right - manageButtonRect.right - manageButtonMargin
+ } else {
+ // Use default padding
+ paddingHorizontal
+ }
+ setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
+ }
+ }
+
fun hide() {
bubbleExpandedView?.taskView?.setObscuredTouchRect(null)
if (visibility != VISIBLE || isHiding) return
@@ -160,9 +219,12 @@
}
private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow)
+ .apply()
}
}
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
\ No newline at end of file
+const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 5e3a077..2cabb65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -21,7 +21,6 @@
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
-import android.view.View.OnKeyListener
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
@@ -30,16 +29,17 @@
import com.android.wm.shell.animation.Interpolators
/**
- * User education view to highlight the collapsed stack of bubbles.
- * Shown only the first time a user taps the stack.
+ * User education view to highlight the collapsed stack of bubbles. Shown only the first time a user
+ * taps the stack.
*/
-class StackEducationView constructor(
+class StackEducationView(
context: Context,
positioner: BubblePositioner,
controller: BubbleController
) : LinearLayout(context) {
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
+ private val TAG =
+ if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
else BubbleDebugConfig.TAG_BUBBLES
private val ANIMATE_DURATION: Long = 200
@@ -69,7 +69,7 @@
override fun setLayoutDirection(layoutDirection: Int) {
super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
+ setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR)
}
override fun onFinishInflate() {
@@ -111,16 +111,16 @@
descTextView.setTextColor(textColor)
}
- private fun setDrawableDirection() {
+ private fun setDrawableDirection(isOnLeft: Boolean) {
view.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
- R.drawable.bubble_stack_user_education_bg
- else R.drawable.bubble_stack_user_education_bg_rtl)
+ if (isOnLeft) R.drawable.bubble_stack_user_education_bg
+ else R.drawable.bubble_stack_user_education_bg_rtl
+ )
}
/**
- * If necessary, shows the user education view for the bubble stack. This appears the first
- * time a user taps on a bubble.
+ * If necessary, shows the user education view for the bubble stack. This appears the first time
+ * a user taps on a bubble.
*
* @return true if user education was shown and wasn't showing before, false otherwise.
*/
@@ -129,29 +129,44 @@
if (visibility == VISIBLE) return false
controller.updateWindowFlagsForBackpress(true /* interceptBack */)
- layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape)
- context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
- else ViewGroup.LayoutParams.MATCH_PARENT
+ layoutParams.width =
+ if (positioner.isLargeScreen || positioner.isLandscape)
+ context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width)
+ else ViewGroup.LayoutParams.MATCH_PARENT
- val stackPadding = context.resources.getDimensionPixelSize(
- R.dimen.bubble_user_education_stack_padding)
+ val isStackOnLeft = positioner.isStackOnLeft(stackPosition)
+ (view.layoutParams as MarginLayoutParams).apply {
+ // Update the horizontal margins depending on the stack position
+ val edgeMargin =
+ resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal)
+ leftMargin = if (isStackOnLeft) 0 else edgeMargin
+ rightMargin = if (isStackOnLeft) edgeMargin else 0
+ }
+
+ val stackPadding =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_user_education_stack_padding)
setAlpha(0f)
setVisibility(View.VISIBLE)
+ setDrawableDirection(isOnLeft = isStackOnLeft)
post {
requestFocus()
with(view) {
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
- setPadding(positioner.bubbleSize + stackPadding, paddingTop, paddingRight,
- paddingBottom)
+ if (isStackOnLeft) {
+ setPadding(
+ positioner.bubbleSize + stackPadding,
+ paddingTop,
+ paddingRight,
+ paddingBottom
+ )
+ translationX = 0f
} else {
- setPadding(paddingLeft, paddingTop, positioner.bubbleSize + stackPadding,
- paddingBottom)
- if (positioner.isLargeScreen || positioner.isLandscape) {
- translationX = (positioner.screenRect.right - width - stackPadding)
- .toFloat()
- } else {
- translationX = 0f
- }
+ setPadding(
+ paddingLeft,
+ paddingTop,
+ positioner.bubbleSize + stackPadding,
+ paddingBottom
+ )
+ translationX = (positioner.screenRect.right - width - stackPadding).toFloat()
}
translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2
}
@@ -168,7 +183,7 @@
* If necessary, hides the stack education view.
*
* @param isExpanding if true this indicates the hide is happening due to the bubble being
- * expanded, false if due to a touch outside of the bubble stack.
+ * expanded, false if due to a touch outside of the bubble stack.
*/
fun hide(isExpanding: Boolean) {
if (visibility != VISIBLE || isHiding) return
@@ -182,9 +197,12 @@
}
private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
+ context
+ .getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+ .edit()
+ .putBoolean(PREF_STACK_EDUCATION, !shouldShow)
+ .apply()
}
}
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
\ No newline at end of file
+const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index b26d061..07c5429 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.unfold;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
@@ -188,23 +189,27 @@
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) {
- if (info.getType() == TRANSIT_CHANGE) {
- // TODO (b/286928742) unfold transition handler should be part of mixed handler to
- // handle merges better.
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo != null
- && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
- // Tasks that are always on top (e.g. bubbles), will handle their own transition
- // as they are on top of everything else. So skip merging transitions here.
- return;
- }
- }
- // Apply changes happening during the unfold animation immediately
- t.apply();
- finishCallback.onTransitionFinished(null);
+ if (info.getType() != TRANSIT_CHANGE) {
+ return;
}
+ if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
+ return;
+ }
+ // TODO (b/286928742) unfold transition handler should be part of mixed handler to
+ // handle merges better.
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Tasks that are always on top (e.g. bubbles), will handle their own transition
+ // as they are on top of everything else. So skip merging transitions here.
+ return;
+ }
+ }
+ // Apply changes happening during the unfold animation immediately
+ t.apply();
+ finishCallback.onTransitionFinished(null);
}
/** Whether `request` contains an unfold action. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 7c6fb99..518f4b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -120,7 +120,7 @@
mWindowSession.grantInputChannel(
mDisplayId,
mDecorationSurface,
- mFakeWindow,
+ mFakeWindow.asBinder(),
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
PRIVATE_FLAG_TRUSTED_OVERLAY,
@@ -155,7 +155,7 @@
mWindowSession.grantInputChannel(
mDisplayId,
mInputSinkSurface,
- mFakeSinkWindow,
+ mFakeSinkWindow.asBinder(),
null /* hostInputToken */,
FLAG_NOT_FOCUSABLE,
0 /* privateFlags */,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index 7917ba6..6d73c12 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.unfold;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.google.common.truth.Truth.assertThat;
@@ -46,6 +47,7 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.TransitionInfoBuilder;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
@@ -265,6 +267,42 @@
verify(finishCallback).onTransitionFinished(any());
}
+ @Test
+ public void mergeAnimation_eatsDisplayOnlyTransitions() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+ TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class);
+
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback);
+
+ // Offer a keyguard unlock transition - this should NOT merge
+ mUnfoldTransitionHandler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(),
+ mock(SurfaceControl.Transaction.class),
+ mTransition,
+ mergeCallback);
+ verify(finishCallback, never()).onTransitionFinished(any());
+
+ // Offer a CHANGE-only transition - this SHOULD merge (b/278064943)
+ mUnfoldTransitionHandler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_CHANGE).build(),
+ mock(SurfaceControl.Transaction.class),
+ mTransition,
+ mergeCallback);
+ verify(mergeCallback).onTransitionFinished(any());
+
+ // We should never have finished the original transition.
+ verify(finishCallback, never()).onTransitionFinished(any());
+ }
+
private TransitionRequestInfo createUnfoldTransitionRequestInfo() {
ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo();
TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange(
@@ -372,4 +410,4 @@
private TransitionInfo createNonUnfoldTransitionInfo() {
return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
}
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
index 985a758..0f48abeb 100644
--- a/media/java/android/media/AudioHalVersionInfo.java
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -54,6 +56,7 @@
flag = false,
prefix = "AUDIO_HAL_TYPE_",
value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+ @Retention(RetentionPolicy.SOURCE)
public @interface AudioHalType {}
/** AudioHalVersionInfo object of all valid Audio HAL versions. */
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index faa7f7f..5331046 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -2018,6 +2018,8 @@
* {@link #HDCP_V2_1},
* {@link #HDCP_V2_2},
* {@link #HDCP_V2_3}
+ *
+ * @removed mistakenly exposed previously
*/
@Deprecated
@Retention(RetentionPolicy.SOURCE)
@@ -2121,6 +2123,8 @@
* {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO},
* {@link #SECURITY_LEVEL_HW_SECURE_DECODE},
* {@link #SECURITY_LEVEL_HW_SECURE_ALL}
+ *
+ * @removed mistakenly exposed previously
*/
@Deprecated
@Retention(RetentionPolicy.SOURCE)
diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java
index 74ca4b8..99fcaf2 100644
--- a/media/java/android/media/Spatializer.java
+++ b/media/java/android/media/Spatializer.java
@@ -267,20 +267,26 @@
HEAD_TRACKING_MODE_DISABLED,
HEAD_TRACKING_MODE_RELATIVE_WORLD,
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
- }) public @interface HeadTrackingMode {};
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HeadTrackingMode {};
/** @hide */
@IntDef(flag = false, value = {
HEAD_TRACKING_MODE_DISABLED,
HEAD_TRACKING_MODE_RELATIVE_WORLD,
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
- }) public @interface HeadTrackingModeSet {};
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HeadTrackingModeSet {};
/** @hide */
@IntDef(flag = false, value = {
HEAD_TRACKING_MODE_RELATIVE_WORLD,
HEAD_TRACKING_MODE_RELATIVE_DEVICE,
- }) public @interface HeadTrackingModeSupported {};
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HeadTrackingModeSupported {};
/**
* @hide
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b0af09c..f4be33c7 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -335,6 +335,13 @@
APerformanceHint_closeSession; # introduced=Tiramisu
APerformanceHint_setThreads; # introduced=UpsideDownCake
APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
+ APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
+ AWorkDuration_create; # introduced=VanillaIceCream
+ AWorkDuration_release; # introduced=VanillaIceCream
+ AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
+ AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream
+ AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream
+ AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream
local:
*;
};
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index c25df6e..c4c8128 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -18,12 +18,14 @@
#include <aidl/android/hardware/power/SessionHint.h>
#include <aidl/android/hardware/power/SessionMode.h>
+#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
+#include <inttypes.h>
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
@@ -75,10 +77,13 @@
int setThreads(const int32_t* threadIds, size_t size);
int getThreadIds(int32_t* const threadIds, size_t* size);
int setPreferPowerEfficiency(bool enabled);
+ int reportActualWorkDuration(AWorkDuration* workDuration);
private:
friend struct APerformanceHintManager;
+ int reportActualWorkDurationInternal(WorkDuration* workDuration);
+
sp<IHintManager> mHintManager;
sp<IHintSession> mHintSession;
// HAL preferred update rate
@@ -92,8 +97,7 @@
// Last hint reported from sendHint indexed by hint value
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
- std::vector<int64_t> mActualDurationsNanos;
- std::vector<int64_t> mTimestampsNanos;
+ std::vector<WorkDuration> mActualWorkDurations;
};
static IHintManager* gIHintManagerForTesting = nullptr;
@@ -195,8 +199,7 @@
* Most of the workload is target_duration dependent, so now clear the cached samples
* as they are most likely obsolete.
*/
- mActualDurationsNanos.clear();
- mTimestampsNanos.clear();
+ mActualWorkDurations.clear();
mFirstTargetMetTimestamp = 0;
mLastTargetMetTimestamp = 0;
return 0;
@@ -207,43 +210,10 @@
ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
return EINVAL;
}
- int64_t now = elapsedRealtimeNano();
- mActualDurationsNanos.push_back(actualDurationNanos);
- mTimestampsNanos.push_back(now);
- if (actualDurationNanos >= mTargetDurationNanos) {
- // Reset timestamps if we are equal or over the target.
- mFirstTargetMetTimestamp = 0;
- } else {
- // Set mFirstTargetMetTimestamp for first time meeting target.
- if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
- (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
- mFirstTargetMetTimestamp = now;
- }
- /**
- * Rate limit the change if the update is over mPreferredRateNanos since first
- * meeting target and less than mPreferredRateNanos since last meeting target.
- */
- if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
- now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
- return 0;
- }
- mLastTargetMetTimestamp = now;
- }
+ WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0);
- binder::Status ret =
- mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
- if (!ret.isOk()) {
- ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
- ret.exceptionMessage().c_str());
- mFirstTargetMetTimestamp = 0;
- mLastTargetMetTimestamp = 0;
- return EPIPE;
- }
- mActualDurationsNanos.clear();
- mTimestampsNanos.clear();
-
- return 0;
+ return reportActualWorkDurationInternal(&workDuration);
}
int APerformanceHintSession::sendHint(SessionHint hint) {
@@ -322,6 +292,67 @@
return OK;
}
+int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) {
+ WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration);
+ if (workDuration->workPeriodStartTimestampNanos <= 0) {
+ ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__);
+ return EINVAL;
+ }
+ if (workDuration->actualTotalDurationNanos <= 0) {
+ ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
+ return EINVAL;
+ }
+ if (workDuration->actualCpuDurationNanos <= 0) {
+ ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__);
+ return EINVAL;
+ }
+ if (workDuration->actualGpuDurationNanos < 0) {
+ ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__);
+ return EINVAL;
+ }
+
+ return reportActualWorkDurationInternal(workDuration);
+}
+
+int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) {
+ int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos;
+ int64_t now = uptimeNanos();
+ workDuration->timestampNanos = now;
+ mActualWorkDurations.push_back(std::move(*workDuration));
+
+ if (actualTotalDurationNanos >= mTargetDurationNanos) {
+ // Reset timestamps if we are equal or over the target.
+ mFirstTargetMetTimestamp = 0;
+ } else {
+ // Set mFirstTargetMetTimestamp for first time meeting target.
+ if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+ (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+ mFirstTargetMetTimestamp = now;
+ }
+ /**
+ * Rate limit the change if the update is over mPreferredRateNanos since first
+ * meeting target and less than mPreferredRateNanos since last meeting target.
+ */
+ if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+ now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+ return 0;
+ }
+ mLastTargetMetTimestamp = now;
+ }
+
+ binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations);
+ if (!ret.isOk()) {
+ ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+ ret.exceptionMessage().c_str());
+ mFirstTargetMetTimestamp = 0;
+ mLastTargetMetTimestamp = 0;
+ return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE;
+ }
+ mActualWorkDurations.clear();
+
+ return 0;
+}
+
// ===================================== C API
APerformanceHintManager* APerformanceHint_getManager() {
return APerformanceHintManager::getInstance();
@@ -376,6 +407,64 @@
return session->setPreferPowerEfficiency(enabled);
}
+int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session,
+ AWorkDuration* workDuration) {
+ if (session == nullptr || workDuration == nullptr) {
+ ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration);
+ return EINVAL;
+ }
+ return session->reportActualWorkDuration(workDuration);
+}
+
+AWorkDuration* AWorkDuration_create() {
+ WorkDuration* workDuration = new WorkDuration();
+ return static_cast<AWorkDuration*>(workDuration);
+}
+
+void AWorkDuration_release(AWorkDuration* aWorkDuration) {
+ if (aWorkDuration == nullptr) {
+ ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__);
+ }
+ delete aWorkDuration;
+}
+
+void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration,
+ int64_t workPeriodStartTimestampNanos) {
+ if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) {
+ ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")",
+ __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos);
+ }
+ static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos =
+ workPeriodStartTimestampNanos;
+}
+
+void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration,
+ int64_t actualTotalDurationNanos) {
+ if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) {
+ ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")",
+ __FUNCTION__, aWorkDuration, actualTotalDurationNanos);
+ }
+ static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos;
+}
+
+void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration,
+ int64_t actualCpuDurationNanos) {
+ if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) {
+ ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")",
+ __FUNCTION__, aWorkDuration, actualCpuDurationNanos);
+ }
+ static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos;
+}
+
+void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration,
+ int64_t actualGpuDurationNanos) {
+ if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) {
+ ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")",
+ __FUNCTION__, aWorkDuration, actualGpuDurationNanos);
+ }
+ static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos;
+}
+
void APerformanceHint_setIHintManagerForTesting(void* iManager) {
delete gHintManagerForTesting;
gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 22d33b1..4553b49 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "PerformanceHintNativeTest"
+#include <android/WorkDuration.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
@@ -60,6 +61,8 @@
MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
MOCK_METHOD(Status, close, (), (override));
MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+ MOCK_METHOD(Status, reportActualWorkDuration2,
+ (const ::std::vector<android::os::WorkDuration>& workDurations), (override));
};
class PerformanceHintTest : public Test {
@@ -120,6 +123,7 @@
std::vector<int64_t> actualDurations;
actualDurations.push_back(20);
EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1));
result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
EXPECT_EQ(0, result);
@@ -238,4 +242,125 @@
APerformanceHintSession* session =
APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
ASSERT_TRUE(session);
-}
\ No newline at end of file
+}
+
+MATCHER_P(WorkDurationEq, expected, "") {
+ if (arg.size() != expected.size()) {
+ *result_listener << "WorkDuration vectors are different sizes. Expected: "
+ << expected.size() << ", Actual: " << arg.size();
+ return false;
+ }
+ for (int i = 0; i < expected.size(); ++i) {
+ android::os::WorkDuration expectedWorkDuration = expected[i];
+ android::os::WorkDuration actualWorkDuration = arg[i];
+ if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) {
+ *result_listener << "WorkDuration at [" << i << "] is different: "
+ << "Expected: " << expectedWorkDuration
+ << ", Actual: " << actualWorkDuration;
+ return false;
+ }
+ }
+ return true;
+}
+
+TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) {
+ APerformanceHintManager* manager = createManager();
+
+ std::vector<int32_t> tids;
+ tids.push_back(1);
+ tids.push_back(2);
+ int64_t targetDuration = 56789L;
+
+ StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+ sp<IHintSession> session_sp(iSession);
+
+ EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+ .Times(Exactly(1))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+ APerformanceHintSession* session =
+ APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+ ASSERT_TRUE(session);
+
+ int64_t targetDurationNanos = 10;
+ EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
+ int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+ EXPECT_EQ(0, result);
+
+ usleep(2); // Sleep for longer than preferredUpdateRateNanos.
+ {
+ std::vector<android::os::WorkDuration> actualWorkDurations;
+ android::os::WorkDuration workDuration(1, 20, 13, 8);
+ actualWorkDurations.push_back(workDuration);
+
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+ .Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration2(session,
+ static_cast<AWorkDuration*>(
+ &workDuration));
+ EXPECT_EQ(0, result);
+ }
+
+ {
+ std::vector<android::os::WorkDuration> actualWorkDurations;
+ android::os::WorkDuration workDuration(-1, 20, 13, 8);
+ actualWorkDurations.push_back(workDuration);
+
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+ .Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration2(session,
+ static_cast<AWorkDuration*>(
+ &workDuration));
+ EXPECT_EQ(22, result);
+ }
+ {
+ std::vector<android::os::WorkDuration> actualWorkDurations;
+ android::os::WorkDuration workDuration(1, -20, 13, 8);
+ actualWorkDurations.push_back(workDuration);
+
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+ .Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration2(session,
+ static_cast<AWorkDuration*>(
+ &workDuration));
+ EXPECT_EQ(22, result);
+ }
+ {
+ std::vector<android::os::WorkDuration> actualWorkDurations;
+ android::os::WorkDuration workDuration(1, 20, -13, 8);
+ actualWorkDurations.push_back(workDuration);
+
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+ .Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration2(session,
+ static_cast<AWorkDuration*>(
+ &workDuration));
+ EXPECT_EQ(EINVAL, result);
+ }
+ {
+ std::vector<android::os::WorkDuration> actualWorkDurations;
+ android::os::WorkDuration workDuration(1, 20, 13, -8);
+ actualWorkDurations.push_back(workDuration);
+
+ EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations)))
+ .Times(Exactly(1));
+ result = APerformanceHint_reportActualWorkDuration2(session,
+ static_cast<AWorkDuration*>(
+ &workDuration));
+ EXPECT_EQ(EINVAL, result);
+ }
+
+ EXPECT_CALL(*iSession, close()).Times(Exactly(1));
+ APerformanceHint_closeSession(session);
+}
+
+TEST_F(PerformanceHintTest, TestAWorkDuration) {
+ AWorkDuration* aWorkDuration = AWorkDuration_create();
+ ASSERT_NE(aWorkDuration, nullptr);
+
+ AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1);
+ AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20);
+ AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13);
+ AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8);
+ AWorkDuration_release(aWorkDuration);
+}
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 58224b8..38bd7d5 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,6 +46,9 @@
"xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
+ "androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-extensions",
],
lint: {
@@ -69,6 +72,9 @@
static_libs: [
"xz-java",
"androidx.leanback_leanback",
+ "androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-extensions",
],
aaptflags: ["--product tablet"],
@@ -94,6 +100,9 @@
"xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
+ "androidx.fragment_fragment",
+ "androidx.lifecycle_lifecycle-livedata",
+ "androidx.lifecycle_lifecycle-extensions",
],
aaptflags: ["--product tv"],
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index a16f9f5..35f5772 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -43,6 +43,11 @@
</intent-filter>
</receiver>
+ <activity android:name=".v2.ui.InstallLaunch"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:theme="@style/Theme.AlertDialogActivity"
+ android:exported="true"/>
+
<activity android:name=".InstallStart"
android:theme="@style/Theme.AlertDialogActivity"
android:exported="true"
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 736e0ef..e2107eb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -40,7 +40,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
+import com.android.packageinstaller.v2.ui.InstallLaunch;
import java.util.Arrays;
/**
@@ -57,9 +57,23 @@
private final boolean mLocalLOGV = false;
+ // TODO (sumedhsen): Replace with an Android Feature Flag once implemented
+ private static final boolean USE_PIA_V2 = false;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ if (USE_PIA_V2) {
+ Intent piaV2 = new Intent(getIntent());
+ piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_NAME, getCallingPackage());
+ piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid());
+ piaV2.setClass(this, InstallLaunch.class);
+ piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(piaV2);
+ finish();
+ return;
+ }
mPackageManager = getPackageManager();
mUserManager = getSystemService(UserManager.class);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
new file mode 100644
index 0000000..03af951
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java
@@ -0,0 +1,376 @@
+/*
+ * 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.packageinstaller.v2.model;
+
+import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery;
+import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner;
+import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested;
+import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted;
+import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
+import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.MutableLiveData;
+import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
+import com.android.packageinstaller.v2.model.installstagedata.InstallReady;
+import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
+import java.io.IOException;
+
+public class InstallRepository {
+
+ private static final String SCHEME_PACKAGE = "package";
+ private static final String TAG = InstallRepository.class.getSimpleName();
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final PackageInstaller mPackageInstaller;
+ private final UserManager mUserManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>();
+ private final boolean mLocalLOGV = false;
+ private Intent mIntent;
+ private boolean mIsSessionInstall;
+ private boolean mIsTrustedSource;
+ /**
+ * Session ID for a session created when caller uses PackageInstaller APIs
+ */
+ private int mSessionId;
+ /**
+ * Session ID for a session created by this app
+ */
+ private int mStagedSessionId = SessionInfo.INVALID_ID;
+ private int mCallingUid;
+ private String mCallingPackage;
+ private SessionStager mSessionStager;
+
+ public InstallRepository(Context context) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mPackageInstaller = mPackageManager.getPackageInstaller();
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mUserManager = context.getSystemService(UserManager.class);
+ }
+
+ /**
+ * Extracts information from the incoming install intent, checks caller's permission to install
+ * packages, verifies that the caller is the install session owner (in case of a session based
+ * install) and checks if the current user has restrictions set that prevent app installation,
+ *
+ * @param intent the incoming {@link Intent} object for installing a package
+ * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName
+ * @return <p>{@link InstallAborted} if there are errors while performing the checks</p>
+ * <p>{@link InstallStaging} after successfully performing the checks</p>
+ */
+ public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) {
+ mIntent = intent;
+
+ String callingAttributionTag = null;
+
+ mIsSessionInstall =
+ PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction())
+ || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
+
+ mSessionId = mIsSessionInstall
+ ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID)
+ : SessionInfo.INVALID_ID;
+
+ mCallingPackage = callerInfo.getPackageName();
+
+ if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) {
+ PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId);
+ mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
+ callingAttributionTag =
+ (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
+ }
+
+ // Uid of the source package, coming from ActivityManager
+ mCallingUid = callerInfo.getUid();
+ if (mCallingUid == Process.INVALID_UID) {
+ Log.e(TAG, "Could not determine the launching uid.");
+ }
+ final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage);
+ // Uid of the source package, with a preference to uid from ApplicationInfo
+ final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid;
+
+ if (mCallingUid == Process.INVALID_UID && sourceInfo == null) {
+ // Caller's identity could not be determined. Abort the install
+ return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
+ }
+
+ if (!isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId)) {
+ return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
+ }
+
+ mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid);
+
+ if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid,
+ mIsTrustedSource)) {
+ return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build();
+ }
+
+ String restriction = getDevicePolicyRestrictions();
+ if (restriction != null) {
+ InstallAborted.Builder abortedBuilder =
+ new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction);
+ final Intent adminSupportDetailsIntent =
+ mDevicePolicyManager.createAdminSupportIntent(restriction);
+ if (adminSupportDetailsIntent != null) {
+ abortedBuilder.setResultIntent(adminSupportDetailsIntent);
+ }
+ return abortedBuilder.build();
+ }
+
+ maybeRemoveInvalidInstallerPackageName(callerInfo);
+
+ return new InstallStaging();
+ }
+
+ /**
+ * @return the ApplicationInfo for the installation source (the calling package), if available
+ */
+ @Nullable
+ private ApplicationInfo getSourceInfo(@Nullable String callingPackage) {
+ if (callingPackage == null) {
+ return null;
+ }
+ try {
+ return mPackageManager.getApplicationInfo(callingPackage, 0);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ return null;
+ }
+ }
+
+ private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent,
+ int originatingUid) {
+ boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
+ return sourceInfo != null && sourceInfo.isPrivilegedApp()
+ && (isNotUnknownSource
+ || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid));
+ }
+
+ private String getDevicePolicyRestrictions() {
+ final String[] restrictions = new String[]{
+ UserManager.DISALLOW_INSTALL_APPS,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
+ };
+
+ for (String restriction : restrictions) {
+ if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) {
+ continue;
+ }
+ return restriction;
+ }
+ return null;
+ }
+
+ private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) {
+ final String installerPackageNameFromIntent =
+ mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ if (installerPackageNameFromIntent == null) {
+ return;
+ }
+ if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName())
+ && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES,
+ callerInfo.getPackageName())) {
+ Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent
+ + " is invalid. Remove it.");
+ EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(),
+ "Invalid EXTRA_INSTALLER_PACKAGE_NAME");
+ mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+ }
+ }
+
+ public void stageForInstall() {
+ Uri uri = mIntent.getData();
+ if (mIsSessionInstall || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) {
+ // For a session based install or installing with a package:// URI, there is no file
+ // for us to stage. Setting the mStagingResult as null will signal InstallViewModel to
+ // proceed with user confirmation stage.
+ mStagingResult.setValue(new InstallReady());
+ return;
+ }
+ if (uri != null
+ && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
+ && canPackageQuery(mContext, mCallingUid, uri)) {
+
+ if (mStagedSessionId > 0) {
+ final PackageInstaller.SessionInfo info =
+ mPackageInstaller.getSessionInfo(mStagedSessionId);
+ if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) {
+ Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring");
+ if (info != null) {
+ cleanupStagingSession();
+ }
+ mStagedSessionId = 0;
+ }
+ }
+
+ // Session does not exist, or became invalid.
+ if (mStagedSessionId <= 0) {
+ // Create session here to be able to show error.
+ try (final AssetFileDescriptor afd =
+ mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) {
+ ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null;
+ PackageInstaller.SessionParams params =
+ createSessionParams(mIntent, pfd, uri.toString());
+ mStagedSessionId = mPackageInstaller.createSession(params);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to create a staging session", e);
+ mStagingResult.setValue(
+ new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
+ .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_APK))
+ .setActivityResultCode(Activity.RESULT_FIRST_USER)
+ .build());
+ return;
+ }
+ }
+
+ SessionStageListener listener = new SessionStageListener() {
+ @Override
+ public void onStagingSuccess(SessionInfo info) {
+ //TODO: Verify if the returned sessionInfo should be used anywhere
+ mStagingResult.setValue(new InstallReady());
+ }
+
+ @Override
+ public void onStagingFailure() {
+ cleanupStagingSession();
+ mStagingResult.setValue(
+ new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR)
+ .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT,
+ PackageManager.INSTALL_FAILED_INVALID_APK))
+ .setActivityResultCode(Activity.RESULT_FIRST_USER)
+ .build());
+ }
+ };
+ if (mSessionStager != null) {
+ mSessionStager.cancel(true);
+ }
+ mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener);
+ mSessionStager.execute();
+ }
+ }
+
+ private void cleanupStagingSession() {
+ if (mStagedSessionId > 0) {
+ try {
+ mPackageInstaller.abandonSession(mStagedSessionId);
+ } catch (SecurityException ignored) {
+ }
+ mStagedSessionId = 0;
+ }
+ }
+
+ private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent,
+ @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) {
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class);
+ params.setPackageSource(
+ referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE
+ : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);
+ params.setInstallAsInstantApp(false);
+ params.setReferrerUri(referrerUri);
+ params.setOriginatingUri(
+ intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class));
+ params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
+ Process.INVALID_UID));
+ params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));
+ params.setInstallReason(PackageManager.INSTALL_REASON_USER);
+ // Disable full screen intent usage by for sideloads.
+ params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
+ PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
+
+ if (pfd != null) {
+ try {
+ final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd,
+ debugPathName, 0);
+ params.setAppPackageName(result.getPackageName());
+ params.setInstallLocation(result.getInstallLocation());
+ params.setSize(result.calculateInstalledSize(params, pfd));
+ } catch (PackageInstaller.PackageParsingException e) {
+ Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e);
+ params.setSize(pfd.getStatSize());
+ } catch (IOException e) {
+ Log.e(TAG,
+ "Cannot calculate installed size " + debugPathName
+ + ". Try only apk size.", e);
+ }
+ } else {
+ Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.");
+ }
+ return params;
+ }
+
+ public MutableLiveData<Integer> getStagingProgress() {
+ if (mSessionStager != null) {
+ return mSessionStager.getProgress();
+ }
+ return new MutableLiveData<>(0);
+ }
+
+ public MutableLiveData<InstallStage> getStagingResult() {
+ return mStagingResult;
+ }
+
+ public interface SessionStageListener {
+
+ void onStagingSuccess(SessionInfo info);
+
+ void onStagingFailure();
+ }
+
+ public static class CallerInfo {
+
+ private final String mPackageName;
+ private final int mUid;
+
+ public CallerInfo(String packageName, int uid) {
+ mPackageName = packageName;
+ mUid = uid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
new file mode 100644
index 0000000..82a8c95
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java
@@ -0,0 +1,215 @@
+/*
+ * 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.packageinstaller.v2.model;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Process;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import java.util.Arrays;
+
+public class PackageUtil {
+
+ private static final String TAG = InstallRepository.class.getSimpleName();
+ private static final String DOWNLOADS_AUTHORITY = "downloads";
+
+ /**
+ * Determines if the UID belongs to the system downloads provider and returns the
+ * {@link ApplicationInfo} of the provider
+ *
+ * @param uid UID of the caller
+ * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a
+ * system app, and its UID matches with the passed UID, null otherwise.
+ */
+ public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
+ final ProviderInfo providerInfo = pm.resolveContentProvider(
+ DOWNLOADS_AUTHORITY, 0);
+ if (providerInfo == null) {
+ // There seems to be no currently enabled downloads provider on the system.
+ return null;
+ }
+ ApplicationInfo appInfo = providerInfo.applicationInfo;
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
+ return appInfo;
+ }
+ return null;
+ }
+
+ /**
+ * Get the maximum target sdk for a UID.
+ *
+ * @param context The context to use
+ * @param uid The UID requesting the install/uninstall
+ * @return The maximum target SDK or -1 if the uid does not match any packages.
+ */
+ public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
+ PackageManager pm = context.getPackageManager();
+ final String[] packages = pm.getPackagesForUid(uid);
+ int targetSdkVersion = -1;
+ if (packages != null) {
+ for (String packageName : packages) {
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore and try the next package
+ }
+ }
+ }
+ return targetSdkVersion;
+ }
+
+ public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) {
+ PackageManager pm = context.getPackageManager();
+ ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(),
+ PackageManager.ComponentInfoFlags.of(0));
+ if (info == null) {
+ return false;
+ }
+ String targetPackage = info.packageName;
+
+ String[] callingPackages = pm.getPackagesForUid(callingUid);
+ if (callingPackages == null) {
+ return false;
+ }
+ for (String callingPackage : callingPackages) {
+ try {
+ if (pm.canPackageQuery(callingPackage, targetPackage)) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // no-op
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param context the {@link Context} object
+ * @param permission the permission name to check
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @return {@code true} if the callingUid is granted the said permission
+ */
+ public static boolean isPermissionGranted(Context context, String permission, int callingUid) {
+ return context.checkPermission(permission, -1, callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * @param pm the {@link PackageManager} object
+ * @param permission the permission name to check
+ * @param packageName the name of the package who's permission is being checked
+ * @return {@code true} if the package is granted the said permission
+ */
+ public static boolean isPermissionGranted(PackageManager pm, String permission,
+ String packageName) {
+ return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * @param context the {@link Context} object
+ * @param callingUid the UID of the caller who's permission is being checked
+ * @param originatingUid the UID from where install is being originated. This could be same as
+ * callingUid or it will be the UID of the package performing a session based install
+ * @param isTrustedSource whether install request is coming from a privileged app or an app that
+ * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted
+ * @return {@code true} if the package is granted the said permission
+ */
+ public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid,
+ int originatingUid, boolean isTrustedSource) {
+ boolean isDocumentsManager =
+ isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid);
+ boolean isSystemDownloadsProvider =
+ getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null;
+
+ if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) {
+
+ final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid);
+ if (targetSdkVersion < 0) {
+ // Invalid originating uid supplied. Abort install.
+ Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid);
+ return false;
+ } else if (targetSdkVersion >= Build.VERSION_CODES.O
+ && !isUidRequestingPermission(context.getPackageManager(), originatingUid,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
+ Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ + Manifest.permission.REQUEST_INSTALL_PACKAGES);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param pm the {@link PackageManager} object
+ * @param uid the UID of the caller who's permission is being checked
+ * @param permission the permission name to check
+ * @return {@code true} if the caller is requesting the said permission in its Manifest
+ */
+ public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) {
+ final String[] packageNames = pm.getPackagesForUid(uid);
+ if (packageNames == null) {
+ return false;
+ }
+ for (final String packageName : packageNames) {
+ final PackageInfo packageInfo;
+ try {
+ packageInfo = pm.getPackageInfo(packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore and try the next package
+ continue;
+ }
+ if (packageInfo.requestedPermissions != null
+ && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param pi the {@link PackageInstaller} object to use
+ * @param originatingUid the UID of the package performing a session based install
+ * @param sessionId ID of the install session
+ * @return {@code true} if the caller is the session owner
+ */
+ public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid,
+ int sessionId) {
+ if (sessionId == SessionInfo.INVALID_ID) {
+ return false;
+ }
+ if (originatingUid == Process.ROOT_UID) {
+ return true;
+ }
+ PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId);
+ if (sessionInfo == null) {
+ return false;
+ }
+ int installerUid = sessionInfo.getInstallerUid();
+ return originatingUid == installerUid;
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
new file mode 100644
index 0000000..a2c81f1
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.packageinstaller.v2.model;
+
+import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
+
+import android.content.Context;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.util.Log;
+import androidx.lifecycle.MutableLiveData;
+import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> {
+
+ private static final String TAG = SessionStager.class.getSimpleName();
+ private final Context mContext;
+ private final Uri mUri;
+ private final int mStagedSessionId;
+ private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0);
+ private final SessionStageListener mListener;
+
+ SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) {
+ mContext = context;
+ mUri = uri;
+ mStagedSessionId = stagedSessionId;
+ mListener = listener;
+ }
+
+ @Override
+ protected PackageInstaller.SessionInfo doInBackground(Void... params) {
+ PackageInstaller pi = mContext.getPackageManager().getPackageInstaller();
+ try (PackageInstaller.Session session = pi.openSession(mStagedSessionId);
+ InputStream in = mContext.getContentResolver().openInputStream(mUri)) {
+ session.setStagingProgress(0);
+
+ if (in == null) {
+ return null;
+ }
+ final long sizeBytes = getContentSizeBytes();
+ mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1);
+
+ long totalRead = 0;
+ try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {
+ byte[] buffer = new byte[1024 * 1024];
+ while (true) {
+ int numRead = in.read(buffer);
+
+ if (numRead == -1) {
+ session.fsync(out);
+ break;
+ }
+
+ if (isCancelled()) {
+ break;
+ }
+
+ out.write(buffer, 0, numRead);
+ if (sizeBytes > 0) {
+ totalRead += numRead;
+ float fraction = ((float) totalRead / (float) sizeBytes);
+ session.setStagingProgress(fraction);
+ publishProgress((int) (fraction * 100.0));
+ }
+ }
+ }
+ return pi.getSessionInfo(mStagedSessionId);
+ } catch (IOException | SecurityException | IllegalStateException
+ | IllegalArgumentException e) {
+ Log.w(TAG, "Error staging apk from content URI", e);
+ return null;
+ }
+ }
+
+ private long getContentSizeBytes() {
+ try (AssetFileDescriptor afd = mContext.getContentResolver()
+ .openAssetFileDescriptor(mUri, "r")) {
+ return afd != null ? afd.getLength() : UNKNOWN_LENGTH;
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to open asset file descriptor", e);
+ return UNKNOWN_LENGTH;
+ }
+ }
+
+ public MutableLiveData<Integer> getProgress() {
+ return mProgressLiveData;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ if (progress != null && progress.length > 0) {
+ mProgressLiveData.setValue(progress[0]);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(SessionInfo sessionInfo) {
+ if (sessionInfo == null || !sessionInfo.isActive()
+ || sessionInfo.getResolvedBaseApkPath() == null) {
+ Log.w(TAG, "Session info is invalid: " + sessionInfo);
+ mListener.onStagingFailure();
+ return;
+ }
+ mListener.onStagingSuccess(sessionInfo);
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
new file mode 100644
index 0000000..cc9857d
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java
@@ -0,0 +1,112 @@
+/*
+ * 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.packageinstaller.v2.model.installstagedata;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class InstallAborted extends InstallStage {
+
+ public static final int ABORT_REASON_INTERNAL_ERROR = 0;
+ public static final int ABORT_REASON_POLICY = 1;
+ private final int mStage = InstallStage.STAGE_ABORTED;
+ private final int mAbortReason;
+
+ /**
+ * It will hold the restriction name, when the restriction was enforced by the system, and not
+ * a device admin.
+ */
+ @NonNull
+ private final String mMessage;
+ /**
+ * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent
+ * to display a support dialog when a feature was disabled by an admin. It will be
+ * {@code null} if the feature is disabled by the system. In this case, the restriction name
+ * will be set in {@link #mMessage} </p>
+ *
+ * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an
+ * intent to be sent as a result to the calling activity.</p>
+ */
+ @Nullable
+ private final Intent mIntent;
+ private final int mActivityResultCode;
+
+ private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent,
+ int activityResultCode) {
+ mAbortReason = reason;
+ mMessage = message;
+ mIntent = intent;
+ mActivityResultCode = activityResultCode;
+ }
+
+ public int getAbortReason() {
+ return mAbortReason;
+ }
+
+ @NonNull
+ public String getMessage() {
+ return mMessage;
+ }
+
+ @Nullable
+ public Intent getResultIntent() {
+ return mIntent;
+ }
+
+ public int getActivityResultCode() {
+ return mActivityResultCode;
+ }
+
+ @Override
+ public int getStageCode() {
+ return mStage;
+ }
+
+ public static class Builder {
+
+ private final int mAbortReason;
+ private String mMessage = "";
+ private Intent mIntent = null;
+ private int mActivityResultCode = Activity.RESULT_CANCELED;
+
+ public Builder(int reason) {
+ mAbortReason = reason;
+ }
+
+ public Builder setMessage(@NonNull String message) {
+ mMessage = message;
+ return this;
+ }
+
+ public Builder setResultIntent(@NonNull Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ public Builder setActivityResultCode(int resultCode) {
+ mActivityResultCode = resultCode;
+ return this;
+ }
+
+ public InstallAborted build() {
+ return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode);
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
similarity index 65%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
index 2529807..548f2c5 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java
@@ -5,7 +5,7 @@
* 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
+ * https://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,
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.packageinstaller.v2.model.installstagedata;
- void configureStream(int width, int height, int format);
+public class InstallReady extends InstallStage{
- void close();
+ private final int mStage = InstallStage.STAGE_READY;
+
+ @Override
+ public int getStageCode() {
+ return mStage;
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
new file mode 100644
index 0000000..f91e64b
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java
@@ -0,0 +1,34 @@
+/*
+ * 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.packageinstaller.v2.model.installstagedata;
+
+public abstract class InstallStage {
+
+ public static final int STAGE_DEFAULT = -1;
+ public static final int STAGE_ABORTED = 0;
+ public static final int STAGE_STAGING = 1;
+ public static final int STAGE_READY = 2;
+ public static final int STAGE_USER_ACTION_REQUIRED = 3;
+ public static final int STAGE_INSTALLING = 4;
+ public static final int STAGE_SUCCESS = 5;
+ public static final int STAGE_FAILED = 6;
+
+ /**
+ * @return the integer value representing current install stage.
+ */
+ public abstract int getStageCode();
+}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
similarity index 70%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
index 2529807..a979cf8 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.packageinstaller.v2.model.installstagedata;
- void configureStream(int width, int height, int format);
+public class InstallStaging extends InstallStage {
- void close();
+ private final int mStage = InstallStage.STAGE_STAGING;
+
+ @Override
+ public int getStageCode() {
+ return mStage;
+ }
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
new file mode 100644
index 0000000..ba5a0cd
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java
@@ -0,0 +1,172 @@
+/*
+ * 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.packageinstaller.v2.ui;
+
+import static android.os.Process.INVALID_UID;
+import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR;
+import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.Window;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.ViewModelProvider;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.InstallRepository;
+import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
+import com.android.packageinstaller.v2.model.installstagedata.InstallAborted;
+import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment;
+import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment;
+import com.android.packageinstaller.v2.viewmodel.InstallViewModel;
+import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory;
+
+public class InstallLaunch extends FragmentActivity {
+
+ public static final String EXTRA_CALLING_PKG_UID =
+ InstallLaunch.class.getPackageName() + ".callingPkgUid";
+ public static final String EXTRA_CALLING_PKG_NAME =
+ InstallLaunch.class.getPackageName() + ".callingPkgName";
+ private static final String TAG = InstallLaunch.class.getSimpleName();
+ private static final String TAG_DIALOG = "dialog";
+ private final boolean mLocalLOGV = false;
+ private InstallViewModel mInstallViewModel;
+ private InstallRepository mInstallRepository;
+
+ private FragmentManager mFragmentManager;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ mFragmentManager = getSupportFragmentManager();
+ mInstallRepository = new InstallRepository(getApplicationContext());
+ mInstallViewModel = new ViewModelProvider(this,
+ new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get(
+ InstallViewModel.class);
+
+ Intent intent = getIntent();
+ CallerInfo info = new CallerInfo(
+ intent.getStringExtra(EXTRA_CALLING_PKG_NAME),
+ intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID));
+ mInstallViewModel.preprocessIntent(intent, info);
+
+ mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange);
+ }
+
+ /**
+ * Main controller of the UI. This method shows relevant dialogs based on the install stage
+ */
+ private void onInstallStageChange(InstallStage installStage) {
+ if (installStage.getStageCode() == InstallStage.STAGE_STAGING) {
+ InstallStagingFragment stagingDialog = new InstallStagingFragment();
+ showDialogInner(stagingDialog);
+ mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress);
+ } else if (installStage.getStageCode() == InstallStage.STAGE_ABORTED) {
+ InstallAborted aborted = (InstallAborted) installStage;
+ switch (aborted.getAbortReason()) {
+ // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR
+ case ABORT_REASON_INTERNAL_ERROR -> setResult(RESULT_CANCELED, true);
+ case ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted);
+ default -> setResult(RESULT_CANCELED, true);
+ }
+ } else {
+ Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode());
+ showDialogInner(null);
+ }
+ }
+
+ private void showPolicyRestrictionDialog(InstallAborted aborted) {
+ String restriction = aborted.getMessage();
+ Intent adminSupportIntent = aborted.getResultIntent();
+ boolean shouldFinish;
+
+ // If the given restriction is set by an admin, display information about the
+ // admin enforcing the restriction for the affected user. If not enforced by the admin,
+ // show the system dialog.
+ if (adminSupportIntent != null) {
+ if (mLocalLOGV) {
+ Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent);
+ }
+ startActivity(adminSupportIntent);
+ // Finish the package installer app since the next dialog will not be shown by this app
+ shouldFinish = true;
+ } else {
+ if (mLocalLOGV) {
+ Log.i(TAG, "Restriction set by system: " + restriction);
+ }
+ DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction);
+ // Don't finish the package installer app since the next dialog
+ // will be shown by this app
+ shouldFinish = false;
+ showDialogInner(blockedByPolicyDialog);
+ }
+ setResult(RESULT_CANCELED, shouldFinish);
+ }
+
+ /**
+ * Create a new dialog based on the install restriction enforced.
+ *
+ * @param restriction The restriction to create the dialog for
+ * @return The dialog
+ */
+ private DialogFragment createDevicePolicyRestrictionDialog(String restriction) {
+ if (mLocalLOGV) {
+ Log.i(TAG, "createDialog(" + restriction + ")");
+ }
+ return switch (restriction) {
+ case UserManager.DISALLOW_INSTALL_APPS ->
+ new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text);
+ case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY ->
+ new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text);
+ default -> null;
+ };
+ }
+
+ /**
+ * Replace any visible dialog by the dialog returned by InstallRepository
+ *
+ * @param newDialog The new dialog to display
+ */
+ private void showDialogInner(@Nullable DialogFragment newDialog) {
+ DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag(
+ TAG_DIALOG);
+ if (currentDialog != null) {
+ currentDialog.dismissAllowingStateLoss();
+ }
+ if (newDialog != null) {
+ newDialog.show(mFragmentManager, TAG_DIALOG);
+ }
+ }
+
+ public void setResult(int resultCode, boolean shouldFinish) {
+ // TODO: This is incomplete. We need to send RESULT_FIRST_USER, RESULT_OK etc
+ // for relevant use cases. Investigate when to send what result.
+ super.setResult(resultCode);
+ if (shouldFinish) {
+ finish();
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java
new file mode 100644
index 0000000..feb2428
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java
@@ -0,0 +1,69 @@
+/*
+ * 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.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ProgressBar;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+
+public class InstallStagingFragment extends DialogFragment {
+
+ private static final String TAG = InstallStagingFragment.class.getSimpleName();
+ private ProgressBar mProgressBar;
+ private AlertDialog mDialog;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
+ dialogView.requireViewById(R.id.staging).setVisibility(View.VISIBLE);
+
+ mDialog = new AlertDialog.Builder(requireContext())
+ .setTitle(getString(R.string.app_name_unknown))
+ .setIcon(R.drawable.ic_file_download)
+ .setView(dialogView)
+ .setNegativeButton(R.string.cancel, null)
+ .setCancelable(false)
+ .create();
+
+ mDialog.setCanceledOnTouchOutside(false);
+ return mDialog;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setEnabled(false);
+ mProgressBar = mDialog.requireViewById(R.id.progress_indeterminate);
+ mProgressBar.setProgress(0);
+ mProgressBar.setMax(100);
+ mProgressBar.setIndeterminate(false);
+ }
+
+ public void setProgress(int progress) {
+ if (mProgressBar != null) {
+ mProgressBar.setProgress(progress);
+ }
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
new file mode 100644
index 0000000..dce0b9a
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java
@@ -0,0 +1,51 @@
+/*
+ * 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.packageinstaller.v2.ui.fragments;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+
+public class SimpleErrorFragment extends DialogFragment {
+
+ private static final String TAG = SimpleErrorFragment.class.getSimpleName();
+ private final int mMessageResId;
+
+ public SimpleErrorFragment(int messageResId) {
+ mMessageResId = messageResId;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new AlertDialog.Builder(getActivity())
+ .setMessage(mMessageResId)
+ .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
+ .create();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ getActivity().setResult(Activity.RESULT_CANCELED);
+ getActivity().finish();
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
new file mode 100644
index 0000000..42b3023
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java
@@ -0,0 +1,70 @@
+/*
+ * 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.packageinstaller.v2.viewmodel;
+
+import android.app.Application;
+import android.content.Intent;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.MediatorLiveData;
+import androidx.lifecycle.MutableLiveData;
+import com.android.packageinstaller.v2.model.InstallRepository;
+import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo;
+import com.android.packageinstaller.v2.model.installstagedata.InstallStage;
+import com.android.packageinstaller.v2.model.installstagedata.InstallStaging;
+
+
+public class InstallViewModel extends AndroidViewModel {
+
+ private static final String TAG = InstallViewModel.class.getSimpleName();
+ private final InstallRepository mRepository;
+ private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>(
+ new InstallStaging());
+
+ public InstallViewModel(@NonNull Application application, InstallRepository repository) {
+ super(application);
+ mRepository = repository;
+ }
+
+ public MutableLiveData<InstallStage> getCurrentInstallStage() {
+ return mCurrentInstallStage;
+ }
+
+ public void preprocessIntent(Intent intent, CallerInfo callerInfo) {
+ InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo);
+ if (stage.getStageCode() == InstallStage.STAGE_ABORTED) {
+ mCurrentInstallStage.setValue(stage);
+ } else {
+ // Since staging is an async operation, we will get the staging result later in time.
+ // Result of the file staging will be set in InstallRepository#mStagingResult.
+ // As such, mCurrentInstallStage will need to add another MutableLiveData
+ // as a data source
+ mRepository.stageForInstall();
+ mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> {
+ if (installStage.getStageCode() != InstallStage.STAGE_READY) {
+ mCurrentInstallStage.setValue(installStage);
+ } else {
+ // Proceed with user confirmation here.
+ }
+ });
+ }
+ }
+
+ public MutableLiveData<Integer> getStagingProgress() {
+ return mRepository.getStagingProgress();
+ }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
new file mode 100644
index 0000000..ef459e6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java
@@ -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.packageinstaller.v2.viewmodel;
+
+import android.app.Application;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import com.android.packageinstaller.v2.model.InstallRepository;
+
+public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory {
+
+ private final InstallRepository mRepository;
+ private final Application mApplication;
+
+ public InstallViewModelFactory(Application application, InstallRepository repository) {
+ // Calling super class' ctor ensures that create method is called correctly and the right
+ // ctor of InstallViewModel is used. If we fail to do that, the default ctor:
+ // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel
+ super(application);
+ mApplication = application;
+ mRepository = repository;
+ }
+
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+ return (T) new InstallViewModel(mApplication, mRepository);
+ }
+}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index fdb0471..905640f 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.1.2"
+agp = "8.1.3"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp
index bd508cb..dbf4ce0 100644
--- a/packages/SettingsLib/Spa/screenshot/Android.bp
+++ b/packages/SettingsLib/Spa/screenshot/Android.bp
@@ -18,6 +18,14 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+filegroup {
+ name: "SpaScreenshotTestRNGFiles",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+}
+
android_test {
name: "SpaScreenshotTests",
use_resource_processor: true,
@@ -36,6 +44,7 @@
"androidx.test.ext.junit",
"androidx.test.runner",
"mockito-target-minus-junit4",
+ "platform-parametric-runner-lib",
"platform-screenshot-diff-core",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp
new file mode 100644
index 0000000..6b8197c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp
@@ -0,0 +1,74 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "SpaRoboRNGTestsAssetsLib",
+ asset_dirs: ["assets"],
+ sdk_version: "current",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ optimize: {
+ enabled: false,
+ },
+ use_resource_processor: true,
+ resource_dirs: ["res"],
+}
+
+android_app {
+ name: "SpaRoboApp",
+ srcs: [],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "flag-junit",
+ "guava",
+ "SpaLib",
+ "SpaLibTestUtils",
+ "SpaRoboRNGTestsAssetsLib",
+ "platform-screenshot-diff-core",
+ "PlatformComposeSceneTransitionLayoutTestsUtils",
+ ],
+ manifest: "robo-manifest.xml",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.settingslib.spa.screenshot",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+ kotlincflags: ["-Xjvm-default=all"],
+
+ plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "SpaRoboRNGTests",
+ srcs: [
+ ":SpaScreenshotTestRNGFiles",
+ ":flag-junit",
+ ":platform-test-screenshot-rules",
+ ],
+ // Do not add any new libraries here, they should be added to SpaRoboApp above.
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "uiautomator-helpers",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+ upstream: true,
+ java_resource_dirs: ["config"],
+ instrumentation_for: "SpaRoboApp",
+}
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..1918e1c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spa.screenshot">
+
+ <uses-sdk android:minSdkVersion="21"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
new file mode 100644
index 0000000..b2f3cf1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png
new file mode 100644
index 0000000..dc7e756
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png
new file mode 100644
index 0000000..95021fa
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png
new file mode 100644
index 0000000..281f572
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png
new file mode 100644
index 0000000..0dc707f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
new file mode 100644
index 0000000..7c135a0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png
new file mode 100644
index 0000000..7c3e993
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
new file mode 100644
index 0000000..7b438c4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
new file mode 100644
index 0000000..ac64619
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
new file mode 100644
index 0000000..5506c8c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
new file mode 100644
index 0000000..bb518c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
new file mode 100644
index 0000000..f4b9063
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..fc60c0f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
new file mode 100644
index 0000000..fa06927
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png
new file mode 100644
index 0000000..698cb4e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png
new file mode 100644
index 0000000..95021fa
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
new file mode 100644
index 0000000..a5f3fa5
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png
new file mode 100644
index 0000000..c1938b4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..81a181f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png
new file mode 100644
index 0000000..ffc6729
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
new file mode 100644
index 0000000..a620040
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
new file mode 100644
index 0000000..0b40aa8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
new file mode 100644
index 0000000..cfe8587
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
new file mode 100644
index 0000000..bb518c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
new file mode 100644
index 0000000..3632755
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..7e5b602
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
new file mode 100644
index 0000000..a3692cd
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
new file mode 100644
index 0000000..233d088
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
new file mode 100644
index 0000000..10869f2
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
new file mode 100644
index 0000000..3eaecc1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
new file mode 100644
index 0000000..ca61911
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
new file mode 100644
index 0000000..8a0da31
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
new file mode 100644
index 0000000..19d0afd
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
new file mode 100644
index 0000000..236a1a0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
new file mode 100644
index 0000000..72b7954
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
new file mode 100644
index 0000000..36486c4
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
new file mode 100644
index 0000000..5bb318f
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
new file mode 100644
index 0000000..fedce44
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
new file mode 100644
index 0000000..3b389d7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml
new file mode 100644
index 0000000..6597ffb
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="412dp"
+ android:height="300dp"
+ android:viewportWidth="412"
+ android:viewportHeight="300">
+ <path
+ android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:pathData="M79.2,179.6h53.6v8.5h-53.6z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M142.5,179.6h30.4v8.5h-30.4z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M79.2,195.5h79.2v8.5h-79.2z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M168.1,195.5h34.1v8.5h-34.1z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M211.9,195.5h34.1v8.5h-34.1z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M182.7,179.6h73.1v8.5h-73.1z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M265.5,179.6h26.8v8.5h-26.8z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M302.1,179.6h26.8v8.5h-26.8z"
+ android:fillColor="#1A73E8"/>
+ <path
+ android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z"
+ android:fillColor="#DADCE0"/>
+</vector>
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml
new file mode 100644
index 0000000..af1a11e
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.settingslib.spa.screenshot"
+ coreApp="true">
+ <application>
+ <activity
+ android:name="androidx.activity.ComponentActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
index 3dcefe9..1cbdc33 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.screenshot.util
+import android.os.Build
import androidx.activity.ComponentActivity
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -49,15 +50,19 @@
)
)
private val composeRule = createAndroidComposeRule<ComponentActivity>()
- private val delegateRule =
- RuleChain.outerRule(colorsRule)
- .around(deviceEmulationRule)
+ private val roboRule =
+ RuleChain.outerRule(deviceEmulationRule)
.around(screenshotRule)
.around(composeRule)
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(roboRule)
private val matcher = UnitTestBitmapMatcher
+ private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
+ val ruleToApply = if (isRobolectric) roboRule else delegateRule
+ return ruleToApply.apply(base, description)
}
/**
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
index b74a243..2cb6044 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt
@@ -26,15 +26,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class ActionButtonsScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
index 2529807..8e55695 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.button;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index 051ef77..7ef9f10 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -25,15 +25,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
index 3822571..3790164 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt
@@ -25,15 +25,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class LineChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
index 6dd62ec..3c3cc85 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt
@@ -23,15 +23,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class PieChartScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
index 2529807..afe3f07 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.chart;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
index 0ccfc0b..616b225 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt
@@ -24,15 +24,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class ImageIllustrationScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
index 2529807..0089c2e 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.illustration;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
index e547d26..8dd4ce7 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt
@@ -23,15 +23,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class MainSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
index dd6b553..1e1a785 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt
@@ -28,15 +28,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
private const val TITLE = "Title"
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
index 357d815..d1878a74 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt
@@ -29,15 +29,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class ProgressBarPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
index fdee7ee..c9f098b 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt
@@ -25,15 +25,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class SliderPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
index 6966a74..eca40fb 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt
@@ -27,15 +27,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class SwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
index 72c5cb8..f81a59f 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt
@@ -24,15 +24,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class TwoTargetSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
index 2529807..fd6a5dd 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.preference;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
index fb01f77..98a4288 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt
@@ -21,15 +21,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class FooterScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
index 2867741..5417095 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt
@@ -22,15 +22,16 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.PhoneAndTabletMinimal
/** A screenshot test for ExampleFeature. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
index 2529807..45210ab 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.settingslib.spa.screenshot.widget.ui;
- void configureStream(int width, int height, int format);
-
- void close();
-}
+import org.robolectric.annotation.GraphicsMode;
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 47660bc..5a1120e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -20,6 +20,7 @@
import androidx.compose.ui.unit.dp
object SettingsDimension {
+ val paddingTiny = 2.dp
val paddingSmall = 4.dp
val itemIconSize = 24.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index f4b2843..b4a6a0d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -39,6 +39,7 @@
fun SettingsTitle(title: String, useMediumWeight: Boolean = false) {
Text(
text = title,
+ modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight),
)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index fa8c1fb..0ffcc45 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -594,6 +594,16 @@
|| cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
+ /**
+ * Check if the Bluetooth device is an active LE Audio device
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @return if the Bluetooth device is an active LE Audio device
+ */
+ public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
+ }
+
private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
if (cachedDevice == null) {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e9f4b5c..245fe6e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1310,7 +1310,7 @@
// Set default string with battery level in device connected situation.
if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
stringRes = R.string.bluetooth_battery_level_untethered;
- } else if (batteryLevelPercentageString != null) {
+ } else if (batteryLevelPercentageString != null && !shortSummary) {
stringRes = R.string.bluetooth_battery_level;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c0d83c4..f1b53ed 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -233,7 +233,6 @@
Settings.Global.DEVELOPMENT_FORCE_RTL,
Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW,
Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR,
- Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR,
Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH,
Settings.Global.DEVICE_DEMO_MODE,
Settings.Global.DISABLE_WINDOW_BLURS,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 80fd516..cf51e21 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -246,11 +246,9 @@
srcs: [
/* Status bar fakes */
"tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
- "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt",
- "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt",
- "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt",
"tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
"tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
+ "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt",
/* QS fakes */
"tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
@@ -263,6 +261,7 @@
srcs: [
/* Keyguard converted tests */
// data
+ "tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -285,6 +284,7 @@
"tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt",
"tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
"tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+ "tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt",
"tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index df22a70..0b13383 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -28,6 +28,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -63,11 +64,13 @@
val isImeVisible by rememberUpdatedState(WindowInsets.imeAnimationTarget.getBottom(density) > 0)
LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) }
- LaunchedEffect(Unit) {
+ DisposableEffect(Unit) {
+ viewModel.onShown()
+
// When the UI comes up, request focus on the TextField to bring up the software keyboard.
focusRequester.requestFocus()
- // Also, report that the UI is shown to let the view-model runs some logic.
- viewModel.onShown()
+
+ onDispose { viewModel.onHidden() }
}
LaunchedEffect(animateFailure) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 03efbe0..2bbe9b8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -26,6 +26,7 @@
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -65,8 +66,10 @@
viewModel: PatternBouncerViewModel,
modifier: Modifier = Modifier,
) {
- // Report that the UI is shown to let the view-model run some logic.
- LaunchedEffect(Unit) { viewModel.onShown() }
+ DisposableEffect(Unit) {
+ viewModel.onShown()
+ onDispose { viewModel.onHidden() }
+ }
val colCount = viewModel.columnCount
val rowCount = viewModel.rowCount
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
index 243751fa..59617c9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -31,6 +31,7 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -69,8 +70,10 @@
viewModel: PinBouncerViewModel,
modifier: Modifier = Modifier,
) {
- // Report that the UI is shown to let the view-model run some logic.
- LaunchedEffect(Unit) { viewModel.onShown() }
+ DisposableEffect(Unit) {
+ viewModel.onShown()
+ onDispose { viewModel.onHidden() }
+ }
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 814ea31..1a97912 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -18,6 +18,11 @@
package com.android.systemui.bouncer.ui.composable
+import android.app.AlertDialog
+import android.app.Dialog
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.TextView
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.tween
@@ -26,11 +31,16 @@
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -41,14 +51,21 @@
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.android.compose.PlatformOutlinedButton
import com.android.compose.animation.Easings
import com.android.keyguard.PinShapeAdapter
import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
@@ -189,6 +206,10 @@
shapeAnimations: ShapeAnimations,
modifier: Modifier = Modifier,
) {
+ if (viewModel.isSimAreaVisible) {
+ SimArea(viewModel = viewModel)
+ }
+
// Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from
// `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove
// animation, thus the composable to be removed has to remain in the composition until fully
@@ -234,6 +255,94 @@
pinInputRow.Content(modifier)
}
+@Composable
+private fun SimArea(viewModel: PinBouncerViewModel) {
+ val isLockedEsim by viewModel.isLockedEsim.collectAsState()
+ val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState()
+ val errorDialogMessage by viewModel.errorDialogMessage.collectAsState()
+ var unlockDialog: Dialog? by remember { mutableStateOf(null) }
+ var errorDialog: Dialog? by remember { mutableStateOf(null) }
+ val context = LocalView.current.context
+
+ DisposableEffect(isSimUnlockingDialogVisible) {
+ if (isSimUnlockingDialogVisible) {
+ val builder =
+ AlertDialog.Builder(context).apply {
+ setMessage(context.getString(R.string.kg_sim_unlock_progress_dialog_message))
+ setCancelable(false)
+ }
+ unlockDialog =
+ builder.create().apply {
+ window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ show()
+ findViewById<TextView>(android.R.id.message)?.gravity = Gravity.CENTER
+ }
+ } else {
+ unlockDialog?.hide()
+ unlockDialog = null
+ }
+
+ onDispose {
+ unlockDialog?.hide()
+ unlockDialog = null
+ }
+ }
+
+ DisposableEffect(errorDialogMessage) {
+ if (errorDialogMessage != null) {
+ val builder = AlertDialog.Builder(context)
+ builder.setMessage(errorDialogMessage)
+ builder.setCancelable(false)
+ builder.setNeutralButton(R.string.ok, null)
+ errorDialog =
+ builder.create().apply {
+ window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ setOnDismissListener { viewModel.onErrorDialogDismissed() }
+ show()
+ }
+ } else {
+ errorDialog?.hide()
+ errorDialog = null
+ }
+
+ onDispose {
+ errorDialog?.hide()
+ errorDialog = null
+ }
+ }
+
+ Box(modifier = Modifier.padding(bottom = 20.dp)) {
+ // If isLockedEsim is null, then we do not show anything.
+ if (isLockedEsim == true) {
+ PlatformOutlinedButton(
+ onClick = { viewModel.onDisableEsimButtonClicked() },
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_no_sim),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
+ )
+ Text(
+ text = stringResource(R.string.disable_carrier_button_text),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+ } else if (isLockedEsim == false) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_lockscreen_sim),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.background_protected))
+ )
+ }
+ }
+}
+
private class PinInputRow(
val shapeAnimations: ShapeAnimations,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
deleted file mode 100644
index c84a5e9..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.qs.footer.ui.compose
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.unit.dp
-import com.android.compose.animation.scene.ElementKey
-import com.android.compose.animation.scene.SceneScope
-
-object QuickSettings {
- object Elements {
- // TODO RENAME
- val Content = ElementKey("QuickSettingsContent")
- val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
- val FooterActions = ElementKey("QuickSettingsFooterActions")
- }
-}
-
-@Composable
-fun SceneScope.QuickSettings(
- modifier: Modifier = Modifier,
-) {
- // TODO(b/272780058): implement.
- Column(
- modifier =
- modifier
- .element(QuickSettings.Elements.Content)
- .fillMaxWidth()
- .defaultMinSize(minHeight = 300.dp)
- .clip(RoundedCornerShape(32.dp))
- .background(MaterialTheme.colorScheme.primary)
- .padding(16.dp),
- ) {
- Text(
- text = "Quick settings grid",
- modifier =
- Modifier.element(QuickSettings.Elements.CollapsedGrid)
- .align(Alignment.CenterHorizontally),
- style = MaterialTheme.typography.titleLarge,
- color = MaterialTheme.colorScheme.onPrimary,
- )
- Spacer(modifier = Modifier.weight(1f))
- Text(
- text = "QS footer actions",
- modifier =
- Modifier.element(QuickSettings.Elements.FooterActions)
- .align(Alignment.CenterHorizontally),
- style = MaterialTheme.typography.titleSmall,
- color = MaterialTheme.colorScheme.onPrimary,
- )
- }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
new file mode 100644
index 0000000..28a4801
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.qs.ui.composable
+
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.theme.colorAttr
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.res.R
+
+object QuickSettings {
+ object Elements {
+ // TODO RENAME
+ val Content = ElementKey("QuickSettingsContent")
+ val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+ val FooterActions = ElementKey("QuickSettingsFooterActions")
+ }
+}
+
+@Composable
+private fun QuickSettingsTheme(content: @Composable () -> Unit) {
+ val context = LocalContext.current
+ val themedContext =
+ remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
+ CompositionLocalProvider(LocalContext provides themedContext) { content() }
+}
+
+@Composable
+fun SceneScope.QuickSettings(
+ modifier: Modifier = Modifier,
+ qsSceneAdapter: QSSceneAdapter,
+ state: QSSceneAdapter.State
+) {
+ // TODO(b/272780058): implement.
+ Column(
+ modifier =
+ modifier
+ .element(QuickSettings.Elements.Content)
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = 300.dp)
+ .clip(RoundedCornerShape(32.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(1.dp),
+ ) {
+ QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state)
+ }
+}
+
+@Composable
+private fun QuickSettingsContent(
+ qsSceneAdapter: QSSceneAdapter,
+ state: QSSceneAdapter.State,
+ modifier: Modifier = Modifier,
+) {
+ val qsView by qsSceneAdapter.qsView.collectAsState(null)
+ QuickSettingsTheme {
+ val context = LocalContext.current
+
+ val frame by remember(context) { mutableStateOf(FrameLayout(context)) }
+
+ LaunchedEffect(key1 = context) {
+ if (qsView == null) {
+ qsSceneAdapter.inflate(context, frame)
+ }
+ }
+ qsView?.let {
+ it.attachToParent(frame)
+ AndroidView(
+ modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
+ factory = { _ ->
+ qsSceneAdapter.setState(state)
+ frame
+ },
+ onRelease = { frame.removeAllViews() },
+ update = { qsSceneAdapter.setState(state) }
+ )
+ }
+ }
+}
+
+private fun View.attachToParent(parent: ViewGroup) {
+ if (this.parent != null && this.parent != parent) {
+ (this.parent as ViewGroup).removeView(this)
+ }
+ if (this.parent != parent) {
+ parent.addView(
+ this,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index a33eac5..b9451d1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -17,43 +17,53 @@
package com.android.systemui.qs.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
-import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
+import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
import com.android.systemui.statusbar.phone.StatusBarLocation
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
@SysUISingleton
class QuickSettingsScene
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val viewModel: QuickSettingsSceneViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -61,14 +71,12 @@
) : ComposableScene {
override val key = SceneKey.QuickSettings
- private val _destinationScenes =
- MutableStateFlow<Map<UserAction, SceneModel>>(
- mapOf(
- UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
- )
- )
- .asStateFlow()
- override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes
+ override val destinationScenes =
+ viewModel.destinationScenes.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = emptyMap(),
+ )
@Composable
override fun SceneScope.Content(
@@ -93,6 +101,9 @@
modifier: Modifier = Modifier,
) {
// TODO(b/280887232): implement the real UI.
+ val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+ val collapsedHeaderHeight =
+ with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
@@ -103,12 +114,27 @@
) {
when (LocalWindowSizeClass.current.widthSizeClass) {
WindowWidthSizeClass.Compact ->
- ExpandedShadeHeader(
- viewModel = viewModel.shadeHeaderViewModel,
- createTintedIconManager = createTintedIconManager,
- createBatteryMeterViewController = createBatteryMeterViewController,
- statusBarIconController = statusBarIconController,
- )
+ AnimatedVisibility(
+ visible = !isCustomizing,
+ enter =
+ expandVertically(
+ animationSpec = tween(1000),
+ initialHeight = { collapsedHeaderHeight },
+ ) + fadeIn(tween(1000)),
+ exit =
+ shrinkVertically(
+ animationSpec = tween(1000),
+ targetHeight = { collapsedHeaderHeight },
+ shrinkTowards = Alignment.Top,
+ ) + fadeOut(tween(1000)),
+ ) {
+ ExpandedShadeHeader(
+ viewModel = viewModel.shadeHeaderViewModel,
+ createTintedIconManager = createTintedIconManager,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ statusBarIconController = statusBarIconController,
+ )
+ }
else ->
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
@@ -118,6 +144,10 @@
)
}
Spacer(modifier = Modifier.height(16.dp))
- QuickSettings()
+ QuickSettings(
+ modifier = Modifier.fillMaxHeight(),
+ viewModel.qsSceneAdapter,
+ QSSceneAdapter.State.QS
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 0da562b..4eb9089 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -18,7 +18,6 @@
package com.android.systemui.scene.ui.composable
-import android.os.SystemProperties
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
@@ -84,7 +83,6 @@
val currentDestinations: Map<UserAction, SceneModel> by
currentScene.destinationScenes.collectAsState()
val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
- val isRibbonEnabled = remember { SystemProperties.getBoolean("flexi.ribbon", false) }
DisposableEffect(viewModel, state) {
viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() })
@@ -137,17 +135,15 @@
}
}
- if (isRibbonEnabled) {
- BottomRightCornerRibbon(
- content = {
- Text(
- text = "flexi\uD83E\uDD43",
- color = Color.White,
- )
- },
- modifier = Modifier.align(Alignment.BottomEnd),
- )
- }
+ BottomRightCornerRibbon(
+ content = {
+ Text(
+ text = "flexi\uD83E\uDD43",
+ color = Color.White,
+ )
+ },
+ modifier = Modifier.align(Alignment.BottomEnd),
+ )
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index 7ecfb62..fadbdce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -4,7 +4,7 @@
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.Shade
fun TransitionBuilder.lockscreenToShadeTransition() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index be85bee..5616175 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -4,7 +4,7 @@
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader
fun TransitionBuilder.shadeToQuickSettingsTransition() {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 13ebdf9..a02f046 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -25,6 +25,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -37,7 +38,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -152,7 +154,11 @@
statusBarIconController = statusBarIconController,
)
Spacer(modifier = Modifier.height(16.dp))
- QuickSettings(modifier = Modifier.height(160.dp))
+ QuickSettings(
+ modifier = Modifier.wrapContentHeight(),
+ viewModel.qsSceneAdapter,
+ QSSceneAdapter.State.QQS
+ )
Spacer(modifier = Modifier.height(16.dp))
Notifications(modifier = Modifier.weight(1f))
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index b77a60b..6153e19 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -18,7 +18,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
@@ -36,7 +35,6 @@
import androidx.compose.ui.geometry.isUnspecified
import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.drawscope.scale
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.IntermediateMeasureScope
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
@@ -47,7 +45,6 @@
import androidx.compose.ui.unit.round
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import com.android.compose.modifiers.thenIf
import com.android.compose.ui.util.lerp
/** An element on screen, that can be composed in one or more scenes. */
@@ -146,8 +143,6 @@
element
}
- val lastSharedValues = element.lastSharedValues
- val lastSceneValues = sceneValues.lastValues
DisposableEffect(scene, sceneValues, element) {
onDispose {
@@ -160,18 +155,6 @@
}
}
- val alpha =
- remember(layoutImpl, element, scene, sceneValues) {
- derivedStateOf { elementAlpha(layoutImpl, element, scene, sceneValues) }
- }
- val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } }
- SideEffect {
- if (isOpaque) {
- lastSharedValues.alpha = 1f
- lastSceneValues.alpha = 1f
- }
- }
-
val drawScale by
remember(layoutImpl, element, scene, sceneValues) {
derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) }
@@ -200,14 +183,6 @@
place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this)
}
}
- .thenIf(!isOpaque) {
- Modifier.graphicsLayer {
- val alpha = alpha.value
- this.alpha = alpha
- lastSharedValues.alpha = alpha
- lastSceneValues.alpha = alpha
- }
- }
.testTag(key.testTag)
}
@@ -325,6 +300,61 @@
}
}
+/**
+ * Whether the element is opaque or not.
+ *
+ * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't
+ * reuse [elementAlpha] and simply check if alpha == 1f because [isElementOpaque] is checked during
+ * placement and we don't want to read the transition progress in that phase.
+ */
+private fun isElementOpaque(
+ layoutImpl: SceneTransitionLayoutImpl,
+ element: Element,
+ scene: Scene,
+ sceneValues: Element.TargetValues,
+): Boolean {
+ val state = layoutImpl.state.transitionState
+
+ if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ return true
+ }
+
+ if (!layoutImpl.isTransitionReady(state)) {
+ val lastValue =
+ sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
+ ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
+
+ return lastValue == 1f
+ }
+
+ val fromScene = state.fromScene
+ val toScene = state.toScene
+ val fromValues = element.sceneValues[fromScene]
+ val toValues = element.sceneValues[toScene]
+
+ if (fromValues == null && toValues == null) {
+ error("This should not happen, element $element is neither in $fromScene or $toScene")
+ }
+
+ val isSharedElement = fromValues != null && toValues != null
+ if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+ return true
+ }
+
+ return layoutImpl.transitions
+ .transitionSpec(fromScene, toScene)
+ .transformations(element.key, scene.key)
+ .alpha == null
+}
+
+/**
+ * Whether the element is opaque or not.
+ *
+ * Important: The logic here should closely match the logic in [isElementOpaque]. Note that we don't
+ * reuse [elementAlpha] in [isElementOpaque] and simply check if alpha == 1f because
+ * [isElementOpaque] is checked during placement and we don't want to read the transition progress
+ * in that phase.
+ */
private fun elementAlpha(
layoutImpl: SceneTransitionLayoutImpl,
element: Element,
@@ -446,6 +476,8 @@
}
val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
+ val lastSharedValues = element.lastSharedValues
+ val lastValues = sceneValues.lastValues
val targetOffset =
computeValue(
layoutImpl,
@@ -456,16 +488,31 @@
idleValue = targetOffsetInScene,
currentValue = { currentOffset },
lastValue = {
- sceneValues.lastValues.offset.takeIf { it.isSpecified }
- ?: element.lastSharedValues.offset.takeIf { it.isSpecified }
- ?: currentOffset
+ lastValues.offset.takeIf { it.isSpecified }
+ ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset
},
::lerp,
)
- element.lastSharedValues.offset = targetOffset
- sceneValues.lastValues.offset = targetOffset
- placeable.place((targetOffset - currentOffset).round())
+ lastSharedValues.offset = targetOffset
+ lastValues.offset = targetOffset
+
+ val offset = (targetOffset - currentOffset).round()
+ if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
+ // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
+ // animated once b/305195729 is fixed. Test that drawing is not invalidated in that
+ // case.
+ placeable.place(offset)
+ lastSharedValues.alpha = 1f
+ lastValues.alpha = 1f
+ } else {
+ placeable.placeWithLayer(offset) {
+ val alpha = elementAlpha(layoutImpl, element, scene, sceneValues)
+ this.alpha = alpha
+ lastSharedValues.alpha = alpha
+ lastValues.alpha = alpha
+ }
+ }
}
}
@@ -527,21 +574,17 @@
error("This should not happen, element $element is neither in $fromScene or $toScene")
}
- // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f]
- // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some
- // similar mechanism.
- val transitionProgress = state.progress
-
// The element is shared: interpolate between the value in fromScene and the value in toScene.
// TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
// elements follow the finger direction.
val isSharedElement = fromValues != null && toValues != null
if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
- return lerp(
- sceneValue(fromValues!!),
- sceneValue(toValues!!),
- transitionProgress,
- )
+ val start = sceneValue(fromValues!!)
+ val end = sceneValue(toValues!!)
+
+ // Make sure we don't read progress if values are the same and we don't need to interpolate,
+ // so we don't invalidate the phase where this is read.
+ return if (start == end) start else lerp(start, end, state.progress)
}
val transformation =
@@ -576,8 +619,15 @@
idleValue,
)
+ // Make sure we don't read progress if values are the same and we don't need to interpolate, so
+ // we don't invalidate the phase where this is read.
+ if (targetValue == idleValue) {
+ return targetValue
+ }
+
+ val progress = state.progress
// TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
- val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress
+ val rangeProgress = transformation.range?.progress(progress) ?: progress
// Interpolate between the value at rest and the value before entering/after leaving.
val isEntering = scene.key == toScene
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
new file mode 100644
index 0000000..6401bb3
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ElementTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Composable
+ @OptIn(ExperimentalComposeUiApi::class)
+ private fun SceneScope.Element(
+ key: ElementKey,
+ size: Dp,
+ offset: Dp,
+ modifier: Modifier = Modifier,
+ onLayout: () -> Unit = {},
+ onPlacement: () -> Unit = {},
+ ) {
+ Box(
+ modifier
+ .offset(offset)
+ .element(key)
+ .intermediateLayout { measurable, constraints ->
+ onLayout()
+ val placement = measurable.measure(constraints)
+ layout(placement.width, placement.height) {
+ onPlacement()
+ placement.place(0, 0)
+ }
+ }
+ .size(size)
+ )
+ }
+
+ @Test
+ fun staticElements_noLayout_noPlacement() {
+ val nFrames = 20
+ val layoutSize = 100.dp
+ val elementSize = 50.dp
+ val elementOffset = 20.dp
+
+ var fooLayouts = 0
+ var fooPlacements = 0
+ var barLayouts = 0
+ var barPlacements = 0
+
+ rule.testTransition(
+ fromSceneContent = {
+ Box(Modifier.size(layoutSize)) {
+ // Shared element.
+ Element(
+ TestElements.Foo,
+ elementSize,
+ elementOffset,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
+
+ // Transformed element
+ Element(
+ TestElements.Bar,
+ elementSize,
+ elementOffset,
+ onLayout = { barLayouts++ },
+ onPlacement = { barPlacements++ },
+ )
+ }
+ },
+ toSceneContent = {
+ Box(Modifier.size(layoutSize)) {
+ // Shared element.
+ Element(TestElements.Foo, elementSize, elementOffset)
+ }
+ },
+ transition = {
+ spec = tween(nFrames * 16)
+
+ // no-op transformations.
+ translate(TestElements.Bar, x = 0.dp, y = 0.dp)
+ scaleSize(TestElements.Bar, width = 1f, height = 1f)
+ },
+ ) {
+ var numberOfLayoutsAfterOneAnimationFrame = 0
+ var numberOfPlacementsAfterOneAnimationFrame = 0
+
+ fun assertNumberOfLayoutsAndPlacements() {
+ assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+ assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+ assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame)
+ }
+
+ at(16) {
+ // Capture the number of layouts and placements that happened after 1 animation
+ // frame.
+ numberOfLayoutsAfterOneAnimationFrame = fooLayouts
+ numberOfPlacementsAfterOneAnimationFrame = fooPlacements
+ }
+ repeat(nFrames - 2) { i ->
+ // Ensure that all animation frames (except the final one) don't relayout or replace
+ // static (shared or transformed) elements.
+ at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() }
+ }
+ }
+ }
+
+ @Test
+ fun onlyMovingElements_noLayout_onlyPlacement() {
+ val nFrames = 20
+ val layoutSize = 100.dp
+ val elementSize = 50.dp
+
+ var fooLayouts = 0
+ var fooPlacements = 0
+ var barLayouts = 0
+ var barPlacements = 0
+
+ rule.testTransition(
+ fromSceneContent = {
+ Box(Modifier.size(layoutSize)) {
+ // Shared element.
+ Element(
+ TestElements.Foo,
+ elementSize,
+ offset = 0.dp,
+ onLayout = { fooLayouts++ },
+ onPlacement = { fooPlacements++ },
+ )
+
+ // Transformed element
+ Element(
+ TestElements.Bar,
+ elementSize,
+ offset = 0.dp,
+ onLayout = { barLayouts++ },
+ onPlacement = { barPlacements++ },
+ )
+ }
+ },
+ toSceneContent = {
+ Box(Modifier.size(layoutSize)) {
+ // Shared element.
+ Element(TestElements.Foo, elementSize, offset = 20.dp)
+ }
+ },
+ transition = {
+ spec = tween(nFrames * 16)
+
+ // Only translate Bar.
+ translate(TestElements.Bar, x = 20.dp, y = 20.dp)
+ scaleSize(TestElements.Bar, width = 1f, height = 1f)
+ },
+ ) {
+ var numberOfLayoutsAfterOneAnimationFrame = 0
+ var lastNumberOfPlacements = 0
+
+ fun assertNumberOfLayoutsAndPlacements() {
+ // The number of layouts have not changed.
+ assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+ assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame)
+
+ // The number of placements have increased.
+ assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements)
+ assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements)
+ lastNumberOfPlacements = fooPlacements
+ }
+
+ at(16) {
+ // Capture the number of layouts and placements that happened after 1 animation
+ // frame.
+ numberOfLayoutsAfterOneAnimationFrame = fooLayouts
+ lastNumberOfPlacements = fooPlacements
+ }
+ repeat(nFrames - 2) { i ->
+ // Ensure that all animation frames (except the final one) only replaced the
+ // elements.
+ at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
index 5db27d8..bac3553 100755
--- a/packages/SystemUI/flag_check.py
+++ b/packages/SystemUI/flag_check.py
@@ -14,7 +14,7 @@
The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
-flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
Some examples below:
@@ -74,11 +74,11 @@
#common_typos_disable
flagName = '([a-zA-z0-9_.])+'
- #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
- stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+ #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
+ stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
#common_typos_enable
- readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD'
+ readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD'
flagRegex = fr'^{field}: .*$'
check_flag = re.compile(flagRegex) #Flag:
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index 2459eea..cb638ee 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -28,26 +28,28 @@
style="@android:style/Widget.DeviceDefault.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:text="@string/app_clips_save_add_to_note"
android:layout_marginStart="8dp"
android:background="@drawable/overlay_button_background"
+ android:paddingHorizontal="24dp"
+ android:text="@string/app_clips_save_add_to_note"
android:textColor="?android:textColorSecondary"
+ app:layout_constraintBottom_toTopOf="@id/preview"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/preview" />
+ app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/cancel"
style="@android:style/Widget.DeviceDefault.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="48dp"
- android:text="@android:string/cancel"
- android:layout_marginStart="6dp"
+ android:layout_marginStart="8dp"
android:background="@drawable/overlay_button_background"
+ android:paddingHorizontal="24dp"
+ android:text="@android:string/cancel"
android:textColor="?android:textColorSecondary"
+ app:layout_constraintBottom_toTopOf="@id/preview"
app:layout_constraintStart_toEndOf="@id/save"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/preview" />
+ app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/preview"
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 6b390b1..c02ffa7 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -39,10 +39,10 @@
import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
@@ -298,7 +298,7 @@
object : KeyguardUpdateMonitorCallback() {
override fun onKeyguardVisibilityChanged(visible: Boolean) {
isKeyguardVisible = visible
- if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
if (!isKeyguardVisible) {
clock?.run {
smallClock.animations.doze(if (isDozing) 1f else 0f)
@@ -345,7 +345,7 @@
parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForDozing(this)
- if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 701f7e5..dedac55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
@@ -366,7 +367,7 @@
}
int getNotificationIconAreaHeight() {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
return 0;
} else if (NotificationIconContainerRefactor.isEnabled()) {
return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0;
@@ -594,7 +595,7 @@
}
private void updateAodIcons() {
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
com.android.systemui.res.R.id.left_aligned_notification_icon_container);
@@ -603,7 +604,6 @@
mAodIconsBindHandle.dispose();
}
if (nic != null) {
- nic.setOnLockScreen(true);
final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind(
nic,
mAodIconsViewModel,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 758d1fe..87d937b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -51,10 +51,9 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ViewHierarchyAnimator;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.ClockController;
@@ -100,7 +99,6 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
- private final FeatureFlags mFeatureFlags;
private final InteractionJankMonitor mInteractionJankMonitor;
private final Rect mClipBounds = new Rect();
private final KeyguardInteractor mKeyguardInteractor;
@@ -136,7 +134,6 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
KeyguardLogger logger,
- FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
KeyguardInteractor keyguardInteractor,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -149,9 +146,8 @@
mConfigurationController = configurationController;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
- featureFlags, logger.getBuffer());
+ logger.getBuffer());
mInteractionJankMonitor = interactionJankMonitor;
- mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
@@ -188,7 +184,7 @@
}
mDumpManager.registerDumpable(getInstanceName(), this);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
startCoroutines(EmptyCoroutineContext.INSTANCE);
}
}
@@ -454,7 +450,7 @@
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(layout);
int guideline;
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
guideline = R.id.split_shade_guideline;
} else {
guideline = R.id.qs_edge_guideline;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index d524e4a..ef65144 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -23,8 +23,7 @@
import android.view.View;
import com.android.app.animation.Interpolators;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.statusbar.StatusBarState;
@@ -55,7 +54,6 @@
private boolean mKeyguardViewVisibilityAnimating;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
- private final FeatureFlags mFeatureFlags;
private final LogBuffer mLogBuffer;
public KeyguardVisibilityHelper(View view,
@@ -63,14 +61,12 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
boolean animateYPos,
- FeatureFlags featureFlags,
LogBuffer logBuffer) {
mView = view;
mKeyguardStateController = keyguardStateController;
mDozeParameters = dozeParameters;
mScreenOffAnimationController = screenOffAnimationController;
mAnimateYPos = animateYPos;
- mFeatureFlags = featureFlags;
mLogBuffer = logBuffer;
}
@@ -167,7 +163,7 @@
animProps,
true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
log("Using GoneToAodTransition");
mKeyguardViewVisibilityAnimating = false;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
rename to packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 59b85d1..b704f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -59,8 +59,8 @@
* when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called.
*/
@SysUISingleton
-public class WindowMagnification implements CoreStartable, CommandQueue.Callbacks {
- private static final String TAG = "WindowMagnification";
+public class Magnification implements CoreStartable, CommandQueue.Callbacks {
+ private static final String TAG = "Magnification";
private final ModeSwitchesController mModeSwitchesController;
private final Context mContext;
@@ -154,7 +154,7 @@
DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
@Inject
- public WindowMagnification(Context context, @Main Handler mainHandler,
+ public Magnification(Context context, @Main Handler mainHandler,
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
@@ -366,49 +366,53 @@
@VisibleForTesting
final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback =
new MagnificationSettingsController.Callback() {
- @Override
- public void onSetMagnifierSize(int displayId, int index) {
- mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
- mA11yLogger.logWithPosition(
- MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
- index
- );
- }
+ @Override
+ public void onSetMagnifierSize(int displayId, int index) {
+ mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
+ mA11yLogger.logWithPosition(
+ MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED,
+ index
+ );
+ }
- @Override
- public void onSetDiagonalScrolling(int displayId, boolean enable) {
- mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
- }
+ @Override
+ public void onSetDiagonalScrolling(int displayId, boolean enable) {
+ mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable));
+ }
- @Override
- public void onEditMagnifierSizeMode(int displayId, boolean enable) {
- mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
- mA11yLogger.log(enable
- ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
- : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
- }
+ @Override
+ public void onEditMagnifierSizeMode(int displayId, boolean enable) {
+ mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
+ mA11yLogger.log(enable
+ ?
+ MagnificationSettingsEvent
+ .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
+ : MagnificationSettingsEvent
+ .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
+ }
- @Override
- public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) {
- if (mWindowMagnificationConnectionImpl != null) {
- mWindowMagnificationConnectionImpl.onPerformScaleAction(
- displayId, scale, updatePersistence);
- }
- mA11yLogger.logThrottled(
- MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
- );
- }
+ @Override
+ public void onMagnifierScale(int displayId, float scale,
+ boolean updatePersistence) {
+ if (mWindowMagnificationConnectionImpl != null) {
+ mWindowMagnificationConnectionImpl.onPerformScaleAction(
+ displayId, scale, updatePersistence);
+ }
+ mA11yLogger.logThrottled(
+ MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED
+ );
+ }
- @Override
- public void onModeSwitch(int displayId, int newMode) {
- mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
- }
+ @Override
+ public void onModeSwitch(int displayId, int newMode) {
+ mHandler.post(() -> onModeSwitchInternal(displayId, newMode));
+ }
- @Override
- public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
- mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
- }
- };
+ @Override
+ public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
+ mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
+ }
+ };
@MainThread
private void onSetMagnifierSizeInternal(int displayId, int index) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index ee7781d..b4530ac 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -34,7 +34,7 @@
* A class to control {@link WindowMagnificationSettings} and receive settings panel callbacks by
* {@link WindowMagnificationSettingsCallback}.
* The settings panel callbacks will be delegated through
- * {@link MagnificationSettingsController.Callback} to {@link WindowMagnification}.
+ * {@link MagnificationSettingsController.Callback} to {@link Magnification}.
*/
public class MagnificationSettingsController implements ComponentCallbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
index 928445b..5666851 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java
@@ -37,12 +37,12 @@
private static final String TAG = "WindowMagnificationConnectionImpl";
private IWindowMagnificationConnectionCallback mConnectionCallback;
- private final WindowMagnification mWindowMagnification;
+ private final Magnification mMagnification;
private final Handler mHandler;
- WindowMagnificationConnectionImpl(@NonNull WindowMagnification windowMagnification,
+ WindowMagnificationConnectionImpl(@NonNull Magnification magnification,
@Main Handler mainHandler) {
- mWindowMagnification = windowMagnification;
+ mMagnification = magnification;
mHandler = mainHandler;
}
@@ -51,56 +51,56 @@
float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY,
IRemoteMagnificationAnimationCallback callback) {
mHandler.post(
- () -> mWindowMagnification.enableWindowMagnification(displayId, scale, centerX,
+ () -> mMagnification.enableWindowMagnification(displayId, scale, centerX,
centerY, magnificationFrameOffsetRatioX,
magnificationFrameOffsetRatioY, callback));
}
@Override
public void setScale(int displayId, float scale) {
- mHandler.post(() -> mWindowMagnification.setScale(displayId, scale));
+ mHandler.post(() -> mMagnification.setScale(displayId, scale));
}
@Override
public void disableWindowMagnification(int displayId,
IRemoteMagnificationAnimationCallback callback) {
- mHandler.post(() -> mWindowMagnification.disableWindowMagnification(displayId,
+ mHandler.post(() -> mMagnification.disableWindowMagnification(displayId,
callback));
}
@Override
public void moveWindowMagnifier(int displayId, float offsetX, float offsetY) {
mHandler.post(
- () -> mWindowMagnification.moveWindowMagnifier(displayId, offsetX, offsetY));
+ () -> mMagnification.moveWindowMagnifier(displayId, offsetX, offsetY));
}
@Override
public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY,
IRemoteMagnificationAnimationCallback callback) {
- mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal(
+ mHandler.post(() -> mMagnification.moveWindowMagnifierToPositionInternal(
displayId, positionX, positionY, callback));
}
@Override
public void showMagnificationButton(int displayId, int magnificationMode) {
mHandler.post(
- () -> mWindowMagnification.showMagnificationButton(displayId, magnificationMode));
+ () -> mMagnification.showMagnificationButton(displayId, magnificationMode));
}
@Override
public void removeMagnificationButton(int displayId) {
mHandler.post(
- () -> mWindowMagnification.removeMagnificationButton(displayId));
+ () -> mMagnification.removeMagnificationButton(displayId));
}
@Override
public void removeMagnificationSettingsPanel(int display) {
- mHandler.post(() -> mWindowMagnification.hideMagnificationSettingsPanel(display));
+ mHandler.post(() -> mMagnification.hideMagnificationSettingsPanel(display));
}
@Override
public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
- mHandler.post(() -> mWindowMagnification.setUserMagnificationScale(
+ mHandler.post(() -> mMagnification.setUserMagnificationScale(
userId, displayId, scale));
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 7769dd9..a42c0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -108,6 +109,9 @@
/** The minimal length of a pattern. */
val minPatternLength: Int
+ /** The minimal length of a password. */
+ val minPasswordLength: Int
+
/** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
val isPinEnhancedPrivacyEnabled: StateFlow<Boolean>
@@ -168,6 +172,7 @@
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
+ mobileConnectionsRepository: MobileConnectionsRepository,
) : AuthenticationRepository {
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
@@ -192,9 +197,11 @@
get() = getSelectedUserInfo().id
override val authenticationMethod: Flow<AuthenticationMethodModel> =
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
+ combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
+ selectedUserInfo,
+ _ ->
+ selectedUserInfo.id
+ }
.flatMapLatest { selectedUserId ->
broadcastDispatcher
.broadcastFlow(
@@ -212,9 +219,12 @@
blockingAuthenticationMethodInternal(selectedUserId)
}
}
+ .distinctUntilChanged()
override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE
+ override val minPasswordLength: Int = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE
+
override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
refreshingFlow(
initialValue = true,
@@ -354,9 +364,9 @@
userId: Int,
): AuthenticationMethodModel {
return when (getSecurityMode.apply(userId)) {
- KeyguardSecurityModel.SecurityMode.PIN,
+ KeyguardSecurityModel.SecurityMode.PIN -> AuthenticationMethodModel.Pin
KeyguardSecurityModel.SecurityMode.SimPin,
- KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+ KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 5eefbf5..c297486 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -200,9 +200,8 @@
// We're being throttled, the UI layer should not have called this; skip the
// attempt.
isThrottled.value -> true
- // The pattern is too short; skip the attempt.
- authMethod == AuthenticationMethodModel.Pattern &&
- input.size < repository.minPatternLength -> true
+ // The input is too short; skip the attempt.
+ input.isTooShort(authMethod) -> true
// Auto-confirm attempt when the feature is not enabled; skip the attempt.
tryAutoConfirm && !isAutoConfirmEnabled.value -> true
// Auto-confirm should skip the attempt if the pin entered is too short.
@@ -247,6 +246,14 @@
}
}
+ private fun List<Any>.isTooShort(authMethod: AuthenticationMethodModel): Boolean {
+ return when (authMethod) {
+ AuthenticationMethodModel.Pattern -> size < repository.minPatternLength
+ AuthenticationMethodModel.Password -> size < repository.minPasswordLength
+ else -> false
+ }
+ }
+
/** Starts refreshing the throttling state every second. */
private suspend fun startThrottlingCountdown() {
cancelThrottlingCountdown()
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index bb5b81d..3552a19 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -37,4 +37,6 @@
object Password : AuthenticationMethodModel(isSecure = true)
object Pattern : AuthenticationMethodModel(isSecure = true)
+
+ object Sim : AuthenticationMethodModel(isSecure = true)
}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt
index 2529807..5fc5101 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt
@@ -13,16 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.systemui.bouncer.data.model
- void configureStream(int width, int height, int format);
-
- void close();
-}
+/** Represents the locked sim card in the Bouncer. */
+data class SimBouncerModel(val isSimPukLocked: Boolean, val subscriptionId: Int)
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt
similarity index 66%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt
index 2529807..3cd88d6 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt
@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
+
+package com.android.systemui.bouncer.data.model
/**
- * Counterpart of ICameraDeviceSession for virtual camera.
+ * Represents the user flow for unlocking a PUK locked sim card.
*
- * @hide
+ * After entering the puk code, we need to enter and confirm a new pin code for the sim card.
*/
-interface IVirtualCameraSession {
-
- void configureStream(int width, int height, int format);
-
- void close();
-}
+data class SimPukInputModel(
+ val enteredSimPuk: String? = null,
+ val enteredSimPin: String? = null,
+)
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt
index 2529807..ff6321c 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt
@@ -12,17 +12,16 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- */
-package android.companion.virtual.camera;
-
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
*
- * @hide
*/
-interface IVirtualCameraSession {
- void configureStream(int width, int height, int format);
+package com.android.systemui.bouncer.data.repository
- void close();
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface BouncerRepositoryModule {
+ @Binds
+ fun provideSimRepository(simRepositoryImpl: SimBouncerRepositoryImpl): SimBouncerRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
new file mode 100644
index 0000000..269878b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.bouncer.data.repository
+
+import android.annotation.SuppressLint
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.bouncer.data.model.SimBouncerModel
+import com.android.systemui.bouncer.data.model.SimPukInputModel
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Handles data layer logic for locked sim cards. */
+interface SimBouncerRepository {
+ /** The subscription id of the current locked sim card. */
+ val subscriptionId: StateFlow<Int>
+ /** The active subscription of the current subscription id. */
+ val activeSubscriptionInfo: StateFlow<SubscriptionInfo?>
+ /**
+ * Determines if current sim card is an esim and is locked.
+ *
+ * A null value indicates that we do not know if we are esim locked or not.
+ */
+ val isLockedEsim: StateFlow<Boolean?>
+ /**
+ * Determines whether the current sim is locked requiring a PUK (Personal Unlocking Key) code.
+ */
+ val isSimPukLocked: StateFlow<Boolean>
+ /**
+ * The error message that should be displayed in an alert dialog.
+ *
+ * A null value indicates that the error dialog is not showing.
+ */
+ val errorDialogMessage: StateFlow<String?>
+ /** The state of the user flow on the SimPuk screen. */
+ val simPukInputModel: SimPukInputModel
+ /** Sets the state of the user flow on the SimPuk screen. */
+ fun setSimPukUserInput(enteredSimPuk: String? = null, enteredSimPin: String? = null)
+ /**
+ * Sets the error message when failing sim verification.
+ *
+ * A null value indicates that there is no error message to show.
+ */
+ fun setSimVerificationErrorMessage(msg: String?)
+}
+
+@SysUISingleton
+class SimBouncerRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Main resources: Resources,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val subscriptionManager: SubscriptionManagerProxy,
+ broadcastDispatcher: BroadcastDispatcher,
+ euiccManager: EuiccManager,
+) : SimBouncerRepository {
+ private val isPukScreenAvailable: Boolean =
+ resources.getBoolean(com.android.internal.R.bool.config_enable_puk_unlock_screen)
+
+ private val simBouncerModel: Flow<SimBouncerModel?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
+ trySend(Unit)
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .map {
+ // Check to see if there is a locked sim puk card.
+ val pukLockedSubId =
+ withContext(backgroundDispatcher) {
+ keyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PUK_REQUIRED
+ )
+ }
+ if (
+ isPukScreenAvailable &&
+ subscriptionManager.isValidSubscriptionId(pukLockedSubId)
+ ) {
+ return@map (SimBouncerModel(isSimPukLocked = true, pukLockedSubId))
+ }
+
+ // If there is no locked sim puk card, check to see if there is a locked sim card.
+ val pinLockedSubId =
+ withContext(backgroundDispatcher) {
+ keyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED
+ )
+ }
+ if (subscriptionManager.isValidSubscriptionId(pinLockedSubId)) {
+ return@map SimBouncerModel(isSimPukLocked = false, pinLockedSubId)
+ }
+
+ return@map null // There is no locked sim.
+ }
+
+ override val subscriptionId: StateFlow<Int> =
+ simBouncerModel
+ .map { state -> state?.subscriptionId ?: INVALID_SUBSCRIPTION_ID }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = INVALID_SUBSCRIPTION_ID,
+ )
+
+ @SuppressLint("MissingPermission")
+ override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> =
+ subscriptionId
+ .map {
+ withContext(backgroundDispatcher) {
+ subscriptionManager.getActiveSubscriptionInfo(it)
+ }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ @SuppressLint("MissingPermission")
+ override val isLockedEsim: StateFlow<Boolean?> =
+ activeSubscriptionInfo
+ .map { info -> info?.let { euiccManager.isEnabled && info.isEmbedded } }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ override val isSimPukLocked: StateFlow<Boolean> =
+ simBouncerModel
+ .map { it?.isSimPukLocked == true }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ private val disableEsimErrorMessage: Flow<String?> =
+ broadcastDispatcher.broadcastFlow(filter = IntentFilter(ACTION_DISABLE_ESIM)) { _, receiver
+ ->
+ if (receiver.resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+ resources.getString(R.string.error_disable_esim_msg)
+ } else {
+ null
+ }
+ }
+
+ private val simVerificationErrorMessage: MutableStateFlow<String?> = MutableStateFlow(null)
+
+ override val errorDialogMessage: StateFlow<String?> =
+ merge(disableEsimErrorMessage, simVerificationErrorMessage)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
+ private var _simPukInputModel: SimPukInputModel = SimPukInputModel()
+ override val simPukInputModel: SimPukInputModel
+ get() = _simPukInputModel
+
+ override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) {
+ _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin)
+ }
+
+ override fun setSimVerificationErrorMessage(msg: String?) {
+ simVerificationErrorMessage.value = msg
+ }
+
+ companion object {
+ const val ACTION_DISABLE_ESIM = "com.android.keyguard.disable_esim"
+ const val INVALID_SUBSCRIPTION_ID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index ff36839..d5ac483 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -26,6 +26,7 @@
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.util.kotlin.pairwise
@@ -52,6 +53,8 @@
private val authenticationInteractor: AuthenticationInteractor,
flags: SceneContainerFlags,
private val falsingInteractor: FalsingInteractor,
+ private val powerInteractor: PowerInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
) {
/** The user-facing message to show in the bouncer. */
@@ -128,6 +131,7 @@
* user's pocket or by the user's face while holding their device up to their ear.
*/
fun onIntentionalUserInput() {
+ powerInteractor.onUserTouch()
falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
}
@@ -145,6 +149,10 @@
)
}
+ fun setMessage(message: String?) {
+ repository.setMessage(message)
+ }
+
/**
* Resets the user-facing message back to the default according to the current authentication
* method.
@@ -183,6 +191,12 @@
if (input.isEmpty()) {
return AuthenticationResult.SKIPPED
}
+
+ if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) {
+ // We authenticate sim in SimInteractor
+ return AuthenticationResult.SKIPPED
+ }
+
// Switching to the application scope here since this method is often called from
// view-models, whose lifecycle (and thus scope) is shorter than this interactor.
// This allows the task to continue running properly even when the calling scope has been
@@ -220,6 +234,7 @@
private fun promptMessage(authMethod: AuthenticationMethodModel): String {
return when (authMethod) {
+ is AuthenticationMethodModel.Sim -> simBouncerInteractor.getDefaultMessage()
is AuthenticationMethodModel.Pin ->
applicationContext.getString(R.string.keyguard_enter_your_pin)
is AuthenticationMethodModel.Password ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
index e398c93..efa7792 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.Intent
import android.telecom.TelecomManager
+import android.telephony.euicc.EuiccManager
import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -47,4 +48,9 @@
): EmergencyAffordanceManager {
return EmergencyAffordanceManager(applicationContext)
}
+
+ @Provides
+ fun provideEuiccManager(@Application applicationContext: Context): EuiccManager {
+ return applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
new file mode 100644
index 0000000..99d1f13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -0,0 +1,340 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.UserHandle
+import android.telephony.PinResult
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import android.text.TextUtils
+import android.util.Log
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.SimBouncerRepository
+import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl
+import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl.Companion.ACTION_DISABLE_ESIM
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.util.icuMessageFormat
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/** Handles domain layer logic for locked sim cards. */
+@SuppressLint("WrongConstant")
+@SysUISingleton
+class SimBouncerInteractor
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val repository: SimBouncerRepository,
+ private val telephonyManager: TelephonyManager,
+ @Main private val resources: Resources,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val euiccManager: EuiccManager,
+ // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
+ mobileConnectionsRepository: MobileConnectionsRepository,
+) {
+ val subId: StateFlow<Int> = repository.subscriptionId
+ val isAnySimSecure: Flow<Boolean> = mobileConnectionsRepository.isAnySimSecure
+ val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
+ val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+
+ /** Returns the default message for the sim pin screen. */
+ fun getDefaultMessage(): String {
+ val isEsimLocked = repository.isLockedEsim.value ?: false
+ val isPuk: Boolean = repository.isSimPukLocked.value
+ val subscriptionId = repository.subscriptionId.value
+
+ if (subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ Log.e(TAG, "Trying to get default message from unknown sub id")
+ return ""
+ }
+
+ var count = telephonyManager.activeModemCount
+ val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
+ val displayName = info?.displayName
+ var msg: String =
+ when {
+ count < 2 && isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint)
+ count < 2 -> resources.getString(R.string.kg_sim_pin_instructions)
+ else -> {
+ when {
+ !TextUtils.isEmpty(displayName) && isPuk ->
+ resources.getString(R.string.kg_puk_enter_puk_hint_multi, displayName)
+ !TextUtils.isEmpty(displayName) ->
+ resources.getString(R.string.kg_sim_pin_instructions_multi, displayName)
+ isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint)
+ else -> resources.getString(R.string.kg_sim_pin_instructions)
+ }
+ }
+ }
+
+ if (isEsimLocked) {
+ msg = resources.getString(R.string.kg_sim_lock_esim_instructions, msg)
+ }
+
+ return msg
+ }
+
+ /** Resets the user flow when the sim screen is puk locked. */
+ fun resetSimPukUserInput() {
+ repository.setSimPukUserInput()
+ // Force a garbage collection in an attempt to erase any sim pin or sim puk codes left in
+ // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+ // dismiss animation janky.
+
+ applicationScope.launch(backgroundDispatcher) {
+ delay(5000)
+ System.gc()
+ System.runFinalization()
+ System.gc()
+ }
+ }
+
+ /** Disables the locked esim card so user can bypass the sim pin screen. */
+ fun disableEsim() {
+ val activeSubscription = repository.activeSubscriptionInfo.value
+ if (activeSubscription == null) {
+ val subId = repository.subscriptionId.value
+ Log.e(TAG, "No active subscription with subscriptionId: $subId")
+ return
+ }
+ val intent = Intent(ACTION_DISABLE_ESIM)
+ intent.setPackage(applicationContext.packageName)
+ val callbackIntent =
+ PendingIntent.getBroadcastAsUser(
+ applicationContext,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ UserHandle.SYSTEM
+ )
+ applicationScope.launch(backgroundDispatcher) {
+ euiccManager.switchToSubscription(
+ INVALID_SUBSCRIPTION_ID,
+ activeSubscription.portIndex,
+ callbackIntent,
+ )
+ }
+ }
+
+ /** Update state when error dialog is dismissed by the user. */
+ fun onErrorDialogDismissed() {
+ repository.setSimVerificationErrorMessage(null)
+ }
+
+ /**
+ * Based on sim state, unlock the locked sim with the given credentials.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ suspend fun verifySim(input: List<Any>): String? {
+ if (repository.isSimPukLocked.value) {
+ return verifySimPuk(input.joinToString(separator = ""))
+ }
+
+ return verifySimPin(input.joinToString(separator = ""))
+ }
+
+ /**
+ * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ private suspend fun verifySimPin(input: String): String? {
+ val subscriptionId = repository.subscriptionId.value
+ // A SIM PIN is 4 to 8 decimal digits according to
+ // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
+ if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
+ return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ }
+ val result =
+ withContext(backgroundDispatcher) {
+ val telephonyManager: TelephonyManager =
+ telephonyManager.createForSubscriptionId(subscriptionId)
+ telephonyManager.supplyIccLockPin(input)
+ }
+ when (result.result) {
+ PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
+ if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
+ // Show a dialog to display the remaining number of attempts to verify the sim
+ // pin to the user.
+ repository.setSimVerificationErrorMessage(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
+ } else {
+ return getPinPasswordErrorMessage(result.attemptsRemaining)
+ }
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Verifies the input and unlocks the locked sim with a puk code instead of pin.
+ *
+ * This occurs after incorrectly verifying the sim pin multiple times.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ private suspend fun verifySimPuk(entry: String): String? {
+ val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
+ val subscriptionId: Int = repository.subscriptionId.value
+
+ // Stage 1: Enter the sim puk code of the sim card.
+ if (enteredSimPuk == null) {
+ if (entry.length >= MIN_SIM_PUK_LENGTH) {
+ repository.setSimPukUserInput(enteredSimPuk = entry)
+ return resources.getString(R.string.kg_puk_enter_pin_hint)
+ } else {
+ return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ }
+ }
+
+ // Stage 2: Set a new sim pin to lock the sim card.
+ if (enteredSimPin == null) {
+ if (entry.length in MIN_SIM_PIN_LENGTH..MAX_SIM_PIN_LENGTH) {
+ repository.setSimPukUserInput(
+ enteredSimPuk = enteredSimPuk,
+ enteredSimPin = entry,
+ )
+ return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ } else {
+ return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ }
+ }
+
+ // Stage 3: Confirm the newly set sim pin.
+ if (repository.simPukInputModel.enteredSimPin != entry) {
+ // The entered sim pins do not match. Enter desired sim pin again to confirm.
+ repository.setSimVerificationErrorMessage(
+ resources.getString(R.string.kg_invalid_confirm_pin_hint)
+ )
+ repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
+ return resources.getString(R.string.kg_puk_enter_pin_hint)
+ }
+
+ val result =
+ withContext(backgroundDispatcher) {
+ val telephonyManager = telephonyManager.createForSubscriptionId(subscriptionId)
+ telephonyManager.supplyIccLockPuk(enteredSimPuk, enteredSimPin)
+ }
+ resetSimPukUserInput()
+
+ when (result.result) {
+ PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
+ // Show a dialog to display the remaining number of attempts to verify the sim
+ // puk to the user.
+ repository.setSimVerificationErrorMessage(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
+ )
+ } else {
+ return getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
+ }
+ else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+
+ return null
+ }
+
+ private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
+ var displayMessage: String =
+ if (attemptsRemaining == 0) {
+ resources.getString(R.string.kg_password_wrong_pin_code_pukked)
+ } else if (attemptsRemaining > 0) {
+ val msgId = R.string.kg_password_default_pin_message
+ icuMessageFormat(resources, msgId, attemptsRemaining)
+ } else {
+ val msgId = R.string.kg_sim_pin_instructions
+ resources.getString(msgId)
+ }
+ if (repository.isLockedEsim.value == true) {
+ displayMessage =
+ resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage)
+ }
+ return displayMessage
+ }
+
+ private fun getPukPasswordErrorMessage(
+ attemptsRemaining: Int,
+ isDefault: Boolean,
+ isEsimLocked: Boolean,
+ ): String {
+ var displayMessage: String =
+ if (attemptsRemaining == 0) {
+ resources.getString(R.string.kg_password_wrong_puk_code_dead)
+ } else if (attemptsRemaining > 0) {
+ val msgId =
+ if (isDefault) R.string.kg_password_default_puk_message
+ else R.string.kg_password_wrong_puk_code
+ icuMessageFormat(resources, msgId, attemptsRemaining)
+ } else {
+ val msgId =
+ if (isDefault) R.string.kg_puk_enter_puk_hint
+ else R.string.kg_password_puk_failed
+ resources.getString(msgId)
+ }
+ if (isEsimLocked) {
+ displayMessage =
+ resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage)
+ }
+ return displayMessage
+ }
+
+ companion object {
+ private const val TAG = "BouncerSimInteractor"
+ const val INVALID_SUBSCRIPTION_ID = SimBouncerRepositoryImpl.INVALID_SUBSCRIPTION_ID
+ const val MIN_SIM_PIN_LENGTH = 4
+ const val MAX_SIM_PIN_LENGTH = 8
+ const val MIN_SIM_PUK_LENGTH = 8
+ const val CRITICAL_NUM_OF_ATTEMPTS = 2
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index f46574c..8024874 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -62,6 +62,13 @@
/** Notifies that the UI has been shown to the user. */
fun onShown() {
+ interactor.resetMessage()
+ }
+
+ /**
+ * Notifies that the UI has been hidden from the user (after any transitions have completed).
+ */
+ fun onHidden() {
clearInput()
interactor.resetMessage()
}
@@ -113,8 +120,6 @@
}
_animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
- // TODO(b/291528545): On success, this should only be cleared after the view is animated
- // away).
clearInput()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 09c94c8..44ddd97 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -64,6 +65,7 @@
users: Flow<List<UserViewModel>>,
userSwitcherMenu: Flow<List<UserActionViewModel>>,
actionButtonInteractor: BouncerActionButtonInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
) {
val selectedUserImage: StateFlow<Bitmap?> =
selectedUser
@@ -259,6 +261,17 @@
viewModelScope = newViewModelScope,
interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationMethod = authenticationMethod
+ )
+ is AuthenticationMethodModel.Sim ->
+ PinBouncerViewModel(
+ applicationContext = applicationContext,
+ viewModelScope = newViewModelScope,
+ interactor = bouncerInteractor,
+ isInputEnabled = isInputEnabled,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationMethod = authenticationMethod,
)
is AuthenticationMethodModel.Password ->
PasswordBouncerViewModel(
@@ -316,6 +329,7 @@
flags: SceneContainerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
actionButtonInteractor: BouncerActionButtonInteractor,
+ simBouncerInteractor: SimBouncerInteractor,
): BouncerViewModel {
return BouncerViewModel(
applicationContext = applicationContext,
@@ -328,6 +342,7 @@
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButtonInteractor = actionButtonInteractor,
+ simBouncerInteractor = simBouncerInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index b2b8049..e25e82f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -14,20 +14,26 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import com.android.keyguard.PinShapeAdapter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.res.R
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the PIN code bouncer UI. */
class PinBouncerViewModel(
@@ -35,13 +41,23 @@
viewModelScope: CoroutineScope,
interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
+ private val simBouncerInteractor: SimBouncerInteractor,
+ authenticationMethod: AuthenticationMethodModel,
) :
AuthMethodBouncerViewModel(
viewModelScope = viewModelScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
) {
-
+ /**
+ * Whether the sim-related UI in the pin view is showing.
+ *
+ * This UI is used to unlock a locked sim.
+ */
+ val isSimAreaVisible = authenticationMethod == AuthenticationMethodModel.Sim
+ val isLockedEsim: StateFlow<Boolean?> = simBouncerInteractor.isLockedEsim
+ val errorDialogMessage: StateFlow<String?> = simBouncerInteractor.errorDialogMessage
+ val isSimUnlockingDialogVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
val pinShapes = PinShapeAdapter(applicationContext)
private val mutablePinInput = MutableStateFlow(PinInputViewModel.empty())
@@ -49,7 +65,13 @@
val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
/** The length of the PIN for which we should show a hint. */
- val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
+ val hintedPinLength: StateFlow<Int?> =
+ if (isSimAreaVisible) {
+ flowOf(null)
+ } else {
+ interactor.hintedPinLength
+ }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
/** Appearance of the backspace button. */
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
@@ -80,10 +102,19 @@
initialValue = ActionButtonAppearance.Hidden,
)
- override val authenticationMethod = AuthenticationMethodModel.Pin
+ override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
+ init {
+ viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+ }
+
+ /** Notifies that the user dismissed the sim pin error dialog. */
+ fun onErrorDialogDismissed() {
+ viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() }
+ }
+
/**
* Whether the digit buttons should be animated when touched. Note that this doesn't affect the
* delete or enter buttons; those should always animate.
@@ -123,7 +154,28 @@
/** Notifies that the user clicked the "enter" button. */
fun onAuthenticateButtonClicked() {
- tryAuthenticate(useAutoConfirm = false)
+ if (authenticationMethod == AuthenticationMethodModel.Sim) {
+ viewModelScope.launch {
+ isSimUnlockingDialogVisible.value = true
+ val msg = simBouncerInteractor.verifySim(getInput())
+ interactor.setMessage(msg)
+ isSimUnlockingDialogVisible.value = false
+ clearInput()
+ }
+ } else {
+ tryAuthenticate(useAutoConfirm = false)
+ }
+ }
+
+ fun onDisableEsimButtonClicked() {
+ viewModelScope.launch { simBouncerInteractor.disableEsim() }
+ }
+
+ /** Resets the sim screen and shows a default message. */
+ private fun onResetSimFlow() {
+ simBouncerInteractor.resetSimPukUserInput()
+ interactor.resetMessage()
+ clearInput()
}
override fun clearInput() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index d8ff535..a0e944b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -22,7 +22,7 @@
import com.android.systemui.ScreenDecorations
import com.android.systemui.SliceBroadcastRelayHandler
import com.android.systemui.accessibility.SystemActions
-import com.android.systemui.accessibility.WindowMagnification
+import com.android.systemui.accessibility.Magnification
import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.BiometricNotificationService
@@ -254,11 +254,11 @@
@ClassKey(VolumeUI::class)
abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable
- /** Inject into WindowMagnification. */
+ /** Inject into Magnification. */
@Binds
@IntoMap
- @ClassKey(WindowMagnification::class)
- abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable
+ @ClassKey(Magnification::class)
+ abstract fun bindMagnification(sysui: Magnification): CoreStartable
/** Inject into WMShell. */
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1f2621d..5f54a98 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -37,13 +37,14 @@
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
+import com.android.systemui.bouncer.data.repository.BouncerRepositoryModule;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
+import com.android.systemui.common.CommonModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
-import com.android.systemui.common.CommonModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
@@ -59,6 +60,7 @@
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
@@ -128,6 +130,7 @@
import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
import com.android.systemui.user.domain.UserDomainLayerModule;
+import com.android.systemui.util.EventLogModule;
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
import com.android.systemui.util.kotlin.CoroutinesModule;
@@ -170,6 +173,7 @@
BiometricsModule.class,
BiometricsDomainLayerModule.class,
BouncerInteractorModule.class,
+ BouncerRepositoryModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
@@ -183,6 +187,7 @@
DisableFlagsModule.class,
DisplayModule.class,
DreamModule.class,
+ EventLogModule.class,
FalsingModule.class,
FlagsModule.class,
FlagDependenciesModule.class,
@@ -190,6 +195,7 @@
KeyEventRepositoryModule.class,
KeyboardModule.class,
KeyguardBlueprintModule.class,
+ KeyguardSectionsModule.class,
LetterboxModule.class,
LogModule.class,
MediaProjectionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 298811b..715fb17 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -74,7 +74,8 @@
repository.isUnlocked,
authenticationInteractor.authenticationMethod,
) { isUnlocked, authenticationMethod ->
- !authenticationMethod.isSecure || isUnlocked
+ (!authenticationMethod.isSecure || isUnlocked) &&
+ authenticationMethod != AuthenticationMethodModel.Sim
}
.stateIn(
scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index 410a0c5..ee48ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -71,6 +71,7 @@
private final Runnable mRestoreComplications = new Runnable() {
@Override
public void run() {
+ Log.d(TAG, "Restoring complications...");
mVisibilityController.setVisibility(View.VISIBLE);
mHidden = false;
}
@@ -83,6 +84,7 @@
// Avoid interfering with the exit animations.
return;
}
+ Log.d(TAG, "Hiding complications...");
mVisibilityController.setVisibility(View.INVISIBLE);
mHidden = true;
if (mHiddenCallback != null) {
@@ -136,9 +138,7 @@
final MotionEvent motionEvent = (MotionEvent) ev;
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
- if (DEBUG) {
- Log.d(TAG, "ACTION_DOWN received");
- }
+ Log.i(TAG, "ACTION_DOWN received");
final ListenableFuture<Boolean> touchCheck = mTouchInsetManager
.checkWithinTouchRegion(Math.round(motionEvent.getX()),
@@ -163,6 +163,8 @@
}, mExecutor);
} else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL
|| motionEvent.getAction() == MotionEvent.ACTION_UP) {
+ Log.i(TAG, "ACTION_CANCEL|ACTION_UP received");
+
// End session and initiate delayed reappearance of the complications.
session.pop();
runAfterHidden(() -> mCancelCallbacks.add(
@@ -179,8 +181,10 @@
private void runAfterHidden(Runnable runnable) {
mExecutor.execute(() -> {
if (mHidden) {
+ Log.i(TAG, "Executing after hidden runnable immediately...");
runnable.run();
} else {
+ Log.i(TAG, "Queuing after hidden runnable...");
mHiddenCallback = runnable;
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index da3fd14..83c16ae 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -17,7 +17,6 @@
package com.android.systemui.flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.Flags as Classic
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
import javax.inject.Inject
@@ -28,9 +27,5 @@
FlagDependenciesBase(featureFlags, handler) {
override fun defineDependencies() {
FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token
-
- // These two flags are effectively linked. We should migrate them to a single aconfig flag.
- Classic.MIGRATE_NSSL dependsOn Classic.MIGRATE_KEYGUARD_STATUS_VIEW
- Classic.MIGRATE_KEYGUARD_STATUS_VIEW dependsOn Classic.MIGRATE_NSSL
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index cd3ecb3..b5fe4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -135,14 +135,6 @@
"lockscreen_custom_clocks"
)
- // TODO(b/286092087): Tracking Bug
- @JvmField
- val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag("enable_system_ui_dream_controller")
-
- // TODO(b/288287730): Tracking Bug
- @JvmField
- val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag("enable_system_ui_dream_hosting")
-
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
@@ -254,14 +246,6 @@
// TODO(b/287268101): Tracking bug.
@JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
- /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
- // TODO(b/288074305): Tracking bug.
- @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl")
-
- /** Migrate the status view from the notification panel to keyguard root view. */
- // TODO(b/291767565): Tracking bug.
- @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view")
-
/** Migrate the status bar view on keyguard from notification panel to keyguard root view. */
// TODO(b/299115332): Tracking Bug.
@JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 8d5d73f..5c76be8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -49,8 +49,10 @@
* [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
* this repository.
*
- * To print all transitions to logcat to help with debugging, run this command: adb shell settings
- * put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command:
+ * ```
+ * adb shell cmd statusbar echo -b KeyguardLog:VERBOSE
+ * ```
*
* This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
*/
@@ -175,9 +177,11 @@
override fun onAnimationStart(animation: Animator) {
emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED))
}
+
override fun onAnimationCancel(animation: Animator) {
endAnimation(lastStep.value, TransitionState.CANCELED)
}
+
override fun onAnimationEnd(animation: Animator) {
endAnimation(1f, TransitionState.FINISHED)
}
@@ -222,7 +226,7 @@
}
private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
- trace(nextStep, isManual)
+ logAndTrace(nextStep, isManual)
val emitted = _transitions.tryEmit(nextStep)
if (!emitted) {
Log.w(TAG, "Failed to emit next value without suspending")
@@ -230,26 +234,22 @@
lastStep = nextStep
}
- private fun trace(step: TransitionStep, isManual: Boolean) {
+ private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
if (step.transitionState == TransitionState.RUNNING) {
return
}
- val traceName =
- "Transition: ${step.from} -> ${step.to} " +
- if (isManual) {
- "(manual)"
- } else {
- ""
- }
+ val manualStr = if (isManual) " (manual)" else ""
+ val traceName = "Transition: ${step.from} -> ${step.to}$manualStr"
+
val traceCookie = traceName.hashCode()
- if (step.transitionState == TransitionState.STARTED) {
- Trace.beginAsyncSection(traceName, traceCookie)
- } else if (
- step.transitionState == TransitionState.FINISHED ||
- step.transitionState == TransitionState.CANCELED
- ) {
- Trace.endAsyncSection(traceName, traceCookie)
+ when (step.transitionState) {
+ TransitionState.STARTED -> Trace.beginAsyncSection(traceName, traceCookie)
+ TransitionState.FINISHED -> Trace.endAsyncSection(traceName, traceCookie)
+ TransitionState.CANCELED -> Trace.endAsyncSection(traceName, traceCookie)
+ else -> {}
}
+
+ Log.i(TAG, "${step.transitionState.name} transition: $step$manualStr")
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index ca882e5..0b6b971 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -54,7 +54,10 @@
fun startToLockscreenTransition() {
scope.launch {
- if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) {
+ if (
+ transitionInteractor.startedKeyguardState.replayCache.last() ==
+ KeyguardState.DREAMING
+ ) {
startTransitionTo(KeyguardState.LOCKSCREEN)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index efbe261..922baa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -24,6 +24,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -40,18 +41,20 @@
@OptIn(ExperimentalCoroutinesApi::class)
val viewParams: Flow<KeyguardSurfaceBehindModel> =
- transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition ->
- if (!isInTransition) {
- defaultParams
- } else {
- combine(
- transitionSpecificViewParams,
- defaultParams,
- ) { transitionParams, defaultParams ->
- transitionParams ?: defaultParams
+ transitionInteractor.isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultParams
+ } else {
+ combine(
+ transitionSpecificViewParams,
+ defaultParams,
+ ) { transitionParams, defaultParams ->
+ transitionParams ?: defaultParams
+ }
}
}
- }
+ .distinctUntilChanged()
val isAnimatingSurface = repository.isAnimatingSurface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 419524fe..a03fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -75,24 +75,6 @@
}
scope.launch {
- interactor.finishedKeyguardTransitionStep.collect {
- logger.log(TAG, VERBOSE, "Finished transition", it)
- }
- }
-
- scope.launch {
- interactor.canceledKeyguardTransitionStep.collect {
- logger.log(TAG, VERBOSE, "Canceled transition", it)
- }
- }
-
- scope.launch {
- interactor.startedKeyguardTransitionStep.collect {
- logger.log(TAG, VERBOSE, "Started transition", it)
- }
- }
-
- scope.launch {
keyguardInteractor.dozeTransitionModel.collect {
logger.log(TAG, VERBOSE, "Doze transition", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index b0b8577..4da48f6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -37,14 +37,14 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to the keyguard transitions. */
@SysUISingleton
@@ -171,16 +171,16 @@
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
/** The destination state of the last started transition. */
- val startedKeyguardState: StateFlow<KeyguardState> =
+ val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
- .stateIn(scope, SharingStarted.Eagerly, OFF)
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
/** The last completed [KeyguardState] transition */
- val finishedKeyguardState: StateFlow<KeyguardState> =
+ val finishedKeyguardState: SharedFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
- .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
/**
* Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
@@ -227,14 +227,13 @@
* state.
*/
fun startDismissKeyguardTransition() {
- when (startedKeyguardState.value) {
+ when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
else ->
Log.e(
"KeyguardTransitionInteractor",
- "We don't know how to dismiss keyguard from state " +
- "${startedKeyguardState.value}"
+ "We don't know how to dismiss keyguard from state $startedState."
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt
new file mode 100644
index 0000000..508fb59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Context
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.provider.Settings.SettingNotFoundException
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+@SysUISingleton
+class NaturalScrollingSettingObserver
+@Inject
+constructor(
+ @Main private val handler: Handler,
+ private val context: Context,
+) {
+ var isNaturalScrollingEnabled = true
+ get() {
+ if (!isInitialized) {
+ isInitialized = true
+ update()
+ }
+ return field
+ }
+
+ private var isInitialized = false
+
+ private val contentObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ update()
+ }
+ }
+
+ init {
+ context.contentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), false,
+ contentObserver)
+ }
+
+ private fun update() {
+ isNaturalScrollingEnabled = try {
+ Settings.System.getIntForUser(context.contentResolver,
+ Settings.System.TOUCHPAD_NATURAL_SCROLLING, UserHandle.USER_CURRENT) == 1
+ } catch (e: SettingNotFoundException) {
+ true
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 7601808..d5ac283 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -53,16 +53,16 @@
modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
): UUID? {
if (
- fromState != transitionInteractor.startedKeyguardState.value &&
- fromState != transitionInteractor.finishedKeyguardState.value
+ fromState != transitionInteractor.startedKeyguardState.replayCache.last() &&
+ fromState != transitionInteractor.finishedKeyguardState.replayCache.last()
) {
Log.e(
name,
"startTransition: We were asked to transition from " +
"$fromState to $toState, however we last finished a transition to " +
- "${transitionInteractor.finishedKeyguardState.value}, " +
+ "${transitionInteractor.finishedKeyguardState.replayCache.last()}, " +
"and last started a transition to " +
- "${transitionInteractor.startedKeyguardState.value}. " +
+ "${transitionInteractor.startedKeyguardState.replayCache.last()}. " +
"Ignoring startTransition, but this should never happen."
)
return null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt
new file mode 100644
index 0000000..23642a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.keyguard.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard shade migration nssl flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardShadeMigrationNssl {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.keyguardShadeMigrationNssl()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
deleted file mode 100644
index 5900a24..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.binder
-
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewPropertyAnimator
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-
-object KeyguardAmbientIndicationAreaViewBinder {
- /**
- * Defines interface for an object that acts as the binding between the view and its view-model.
- *
- * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after
- * it is bound.
- */
- interface Binding {
- /**
- * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the
- * indication areas.
- */
- fun getIndicationAreaAnimators(): List<ViewPropertyAnimator>
-
- /** Notifies that device configuration has changed. */
- fun onConfigurationChanged()
-
- /** Destroys this binding, releases resources, and cancels any coroutines. */
- fun destroy()
- }
-
- @OptIn(ExperimentalCoroutinesApi::class)
- fun bind(
- view: ViewGroup,
- viewModel: KeyguardAmbientIndicationViewModel,
- keyguardRootViewModel: KeyguardRootViewModel,
- ): Binding {
- val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
- val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
-
- val disposableHandle =
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- keyguardRootViewModel.alpha.collect { alpha ->
- ambientIndicationArea?.apply {
- this.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
- }
- this.alpha = alpha
- }
- }
- }
-
- launch {
- viewModel.indicationAreaTranslationX.collect { translationX ->
- ambientIndicationArea?.translationX = translationX
- }
- }
-
- launch {
- configurationBasedDimensions
- .map { it.defaultBurnInPreventionYOffsetPx }
- .flatMapLatest { defaultBurnInOffsetY ->
- viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
- }
- .collect { translationY ->
- ambientIndicationArea?.translationY = translationY
- }
- }
-
- }
- }
-
-
- return object : Binding {
- override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
- return listOf(ambientIndicationArea).mapNotNull { it?.animate() }
- }
-
- override fun onConfigurationChanged() {
- configurationBasedDimensions.value = loadFromResources(view)
- }
-
- override fun destroy() {
- disposableHandle.dispose()
- }
- }
- }
-
- private fun loadFromResources(view: View): ConfigurationBasedDimensions {
- return ConfigurationBasedDimensions(
- defaultBurnInPreventionYOffsetPx =
- view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset),
- )
- }
-
- private data class ConfigurationBasedDimensions(
- val defaultBurnInPreventionYOffsetPx: Int,
- )
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 1f74bb6..4de9fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -41,7 +41,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.flags.RefactorFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
@@ -116,7 +116,7 @@
launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } }
}
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
launch {
viewModel.burnInLayerVisibility.collect { visibility ->
childViews[burnInLayerId]?.visibility = visibility
@@ -342,7 +342,7 @@
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
): DisposableHandle? {
- RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode()
+ KeyguardShadeMigrationNssl.assertInLegacyMode()
if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null
return view.repeatWhenAttached {
lifecycleScope.launch {
@@ -368,7 +368,7 @@
iconsAppearTranslationPx: Int,
screenOffAnimationController: ScreenOffAnimationController,
) {
- val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
+ val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled
animate().cancel()
val animatorListener =
object : AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index bdd9a6bf..a2e930c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -49,7 +49,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -445,7 +445,7 @@
private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
largeClockHostView =
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large)
} else {
val hostView = FrameLayout(previewContext)
@@ -460,7 +460,7 @@
largeClockHostView.isInvisible = true
smallClockHostView =
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view)
} else {
val resources = parentView.resources
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 2e64c41..0cf891c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -20,10 +20,10 @@
import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -31,8 +31,13 @@
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION
import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import java.util.Optional
import javax.inject.Inject
+import javax.inject.Named
+import kotlin.jvm.optionals.getOrNull
/**
* Positions elements of the lockscreen to the default position.
@@ -47,7 +52,8 @@
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
defaultShortcutsSection: DefaultShortcutsSection,
- defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+ @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+ defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
defaultStatusViewSection: DefaultStatusViewSection,
defaultStatusBarSection: DefaultStatusBarSection,
@@ -61,11 +67,11 @@
override val id: String = DEFAULT
override val sections =
- listOf(
+ listOfNotNull(
defaultIndicationAreaSection,
defaultDeviceEntryIconSection,
defaultShortcutsSection,
- defaultAmbientIndicationAreaSection,
+ defaultAmbientIndicationAreaSection.getOrNull(),
defaultSettingsPopupMenuSection,
defaultStatusViewSection,
defaultStatusBarSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index d8b368b..14e8f89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -19,18 +19,22 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
import javax.inject.Inject
+import javax.inject.Named
/** Vertically aligns the shortcuts with the udfps. */
@SysUISingleton
@@ -39,7 +43,8 @@
constructor(
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
- defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+ @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+ defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
defaultStatusViewSection: DefaultStatusViewSection,
@@ -52,10 +57,10 @@
override val id: String = SHORTCUTS_BESIDE_UDFPS
override val sections =
- listOf(
+ listOfNotNull(
defaultIndicationAreaSection,
defaultDeviceEntryIconSection,
- defaultAmbientIndicationAreaSection,
+ defaultAmbientIndicationAreaSection.getOrNull(),
defaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection,
defaultStatusViewSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 35679b8..0d397bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -20,18 +20,22 @@
import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
+import com.android.systemui.util.kotlin.getOrNull
+import java.util.Optional
import javax.inject.Inject
+import javax.inject.Named
/**
* Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape
@@ -45,7 +49,8 @@
defaultIndicationAreaSection: DefaultIndicationAreaSection,
defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection,
defaultShortcutsSection: DefaultShortcutsSection,
- defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
+ @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+ defaultAmbientIndicationAreaSection: Optional<KeyguardSection>,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
defaultStatusViewSection: DefaultStatusViewSection,
defaultStatusBarSection: DefaultStatusBarSection,
@@ -58,11 +63,11 @@
override val id: String = ID
override val sections =
- listOf(
+ listOfNotNull(
defaultIndicationAreaSection,
defaultDeviceEntryIconSection,
defaultShortcutsSection,
- defaultAmbientIndicationAreaSection,
+ defaultAmbientIndicationAreaSection.getOrNull(),
defaultSettingsPopupMenuSection,
defaultStatusViewSection,
defaultStatusBarSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index eb01d4f6..b7a165c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -28,6 +28,8 @@
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.res.R
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -46,6 +48,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
+ private val featureFlags: FeatureFlagsClassic,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (keyguardBottomAreaRefactor()) {
@@ -83,20 +86,26 @@
val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
+ val lockIconViewId = if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) {
+ R.id.device_entry_icon_view
+ } else {
+ R.id.lock_icon_view
+ }
+
constraintSet.apply {
constrainWidth(R.id.start_button, width)
constrainHeight(R.id.start_button, height)
connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
- connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT)
- connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP)
- connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+ connect(R.id.start_button, RIGHT, lockIconViewId, LEFT)
+ connect(R.id.start_button, TOP, lockIconViewId, TOP)
+ connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM)
constrainWidth(R.id.end_button, width)
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
- connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT)
- connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP)
- connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
+ connect(R.id.end_button, LEFT, lockIconViewId, RIGHT)
+ connect(R.id.end_button, TOP, lockIconViewId, TOP)
+ connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 09caf45..484d351 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -22,8 +22,7 @@
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import javax.inject.Inject
@@ -33,11 +32,10 @@
@Inject
constructor(
private val context: Context,
- private val featureFlags: FeatureFlags,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
@@ -53,13 +51,13 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 68e16ba..12de185 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -29,6 +29,7 @@
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
@@ -62,7 +63,7 @@
private lateinit var nic: NotificationIconContainer
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
nic =
@@ -81,12 +82,11 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
if (NotificationIconContainerRefactor.isEnabled) {
- nic.setOnLockScreen(true)
nicBindingDisposable?.dispose()
nicBindingDisposable =
NotificationIconContainerViewBinder.bind(
@@ -103,7 +103,7 @@
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
val bottomMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
deleted file mode 100644
index 20cb9b0..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.view.LayoutInflater
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.res.R
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
-import javax.inject.Inject
-
-class DefaultAmbientIndicationAreaSection
-@Inject
-constructor(
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
-) : KeyguardSection() {
- private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
-
- override fun addViews(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
- val view =
- LayoutInflater.from(constraintLayout.context)
- .inflate(R.layout.ambient_indication, constraintLayout, false)
-
- constraintLayout.addView(view)
- }
- }
-
- override fun bindData(constraintLayout: ConstraintLayout) {
- if (keyguardBottomAreaRefactor()) {
- ambientIndicationAreaHandle =
- KeyguardAmbientIndicationAreaViewBinder.bind(
- constraintLayout,
- keyguardAmbientIndicationViewModel,
- keyguardRootViewModel,
- )
- }
- }
-
- override fun applyConstraints(constraintSet: ConstraintSet) {
- constraintSet.apply {
- constrainWidth(R.id.ambient_indication_container, MATCH_PARENT)
-
- if (keyguardUpdateMonitor.isUdfpsSupported) {
- // constrain below udfps and above indication area
- constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT)
- connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM)
- connect(
- R.id.ambient_indication_container,
- BOTTOM,
- R.id.keyguard_indication_area,
- TOP
- )
- connect(R.id.ambient_indication_container, START, PARENT_ID, START)
- connect(R.id.ambient_indication_container, END, PARENT_ID, END)
- } else {
- // constrain above lock icon
- constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT)
- connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP)
- connect(R.id.ambient_indication_container, START, PARENT_ID, START)
- connect(R.id.ambient_indication_container, END, PARENT_ID, END)
- }
- }
- }
-
- override fun removeViews(constraintLayout: ConstraintLayout) {
- ambientIndicationAreaHandle?.destroy()
-
- constraintLayout.removeView(R.id.ambient_indication_container)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index c2aedca..165ee36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
@@ -40,7 +41,7 @@
@Inject
constructor(
context: Context,
- featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags,
notificationPanelView: NotificationPanelView,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -50,7 +51,6 @@
) :
NotificationStackScrollLayoutSection(
context,
- featureFlags,
notificationPanelView,
sharedNotificationContainer,
sharedNotificationContainerViewModel,
@@ -58,7 +58,7 @@
notificationStackSizeCalculator,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 0b0f21d..4abcca9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -31,9 +31,8 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.keyguard.KeyguardStatusView
import com.android.keyguard.dagger.KeyguardStatusViewComponent
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.res.R
@@ -49,7 +48,6 @@
@Inject
constructor(
private val context: Context,
- private val featureFlags: FeatureFlags,
private val notificationPanelView: NotificationPanelView,
private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>,
@@ -60,7 +58,7 @@
private val statusViewId = R.id.keyguard_status_view
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
// At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
@@ -82,7 +80,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let {
val statusViewComponent =
keyguardStatusViewComponentFactory.build(it, context.display)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
new file mode 100644
index 0000000..37c00b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.keyguard.ui.view.layout.sections
+
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import dagger.BindsOptionalOf
+import dagger.Module
+import javax.inject.Named
+
+@Module
+abstract class KeyguardSectionsModule {
+
+ companion object {
+ const val KEYGUARD_AMBIENT_INDICATION_AREA_SECTION =
+ "keyguard_ambient_indication_area_section"
+ }
+
+ @BindsOptionalOf
+ @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION)
+ abstract fun defaultAmbientIndicationAreaSection(): KeyguardSection
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index ea2bdf7..441f59d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -21,8 +21,7 @@
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
@@ -36,7 +35,6 @@
abstract class NotificationStackScrollLayoutSection
constructor(
protected val context: Context,
- protected val featureFlags: FeatureFlags,
private val notificationPanelView: NotificationPanelView,
private val sharedNotificationContainer: SharedNotificationContainer,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -47,7 +45,7 @@
private var disposableHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
// This moves the existing NSSL view to a different parent, as the controller is a
@@ -62,7 +60,7 @@
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
disposableHandle?.dispose()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index dc2ad8d..2c45da6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
@@ -40,7 +41,7 @@
@Inject
constructor(
context: Context,
- featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags,
notificationPanelView: NotificationPanelView,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
@@ -50,7 +51,6 @@
) :
NotificationStackScrollLayoutSection(
context,
- featureFlags,
notificationPanelView,
sharedNotificationContainer,
sharedNotificationContainerViewModel,
@@ -58,7 +58,7 @@
notificationStackSizeCalculator,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
return
}
constraintSet.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
deleted file mode 100644
index dd3967a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import javax.inject.Inject
-
-class KeyguardAmbientIndicationViewModel
-@Inject
-constructor(
- private val keyguardInteractor: KeyguardInteractor,
- private val burnInHelperWrapper: BurnInHelperWrapper,
-) {
-
- /** An observable for the x-offset by which the indication area should be translated. */
- val indicationAreaTranslationX: Flow<Float> =
- keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
-
- /** Returns an observable for the y-offset by which the indication area should be translated. */
- fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return keyguardInteractor.dozeAmount
- .map { dozeAmount ->
- dozeAmount *
- (burnInHelperWrapper.burnInOffset(
- /* amplitude = */ defaultBurnInOffset * 2,
- /* xAxis= */ false,
- ) - defaultBurnInOffset)
- }
- .distinctUntilChanged()
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index b1ff708..9d6e9b4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -46,8 +46,7 @@
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.ShadeStateEvents
-import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -103,7 +102,7 @@
private val communalInteractor: CommunalInteractor,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- panelEventsEvents: ShadeStateEvents,
+ shadeInteractor: ShadeInteractor,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
@Application private val coroutineScope: CoroutineScope,
@@ -545,14 +544,12 @@
mediaHosts.forEach { it?.updateViewVisibility() }
}
- panelEventsEvents.addShadeStateEventsListener(
- object : ShadeStateEventsListener {
- override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
- skipQqsOnExpansion = isExpandImmediateEnabled
- updateDesiredLocation()
- }
+ coroutineScope.launch {
+ shadeInteractor.isQsBypassingShade.collect { isExpandImmediateEnabled ->
+ skipQqsOnExpansion = isExpandImmediateEnabled
+ updateDesiredLocation()
}
- )
+ }
val settingsObserver: ContentObserver =
object : ContentObserver(handler) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 8453af1..0f54e93 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -80,12 +80,13 @@
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
+ val singleAppOptionDisabled =
+ appName != null &&
+ mediaProjectionConfig?.regionToCapture ==
+ MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+
val singleAppDisabledText =
- if (
- appName != null &&
- mediaProjectionConfig?.regionToCapture ==
- MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
- ) {
+ if (singleAppOptionDisabled) {
context.getString(
R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
appName
@@ -93,19 +94,26 @@
} else {
null
}
- return listOf(
- ScreenShareOption(
- mode = SINGLE_APP,
- spinnerText = R.string.screen_share_permission_dialog_option_single_app,
- warningText = singleAppWarningText,
- spinnerDisabledText = singleAppDisabledText,
- ),
- ScreenShareOption(
- mode = ENTIRE_SCREEN,
- spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
- warningText = entireScreenWarningText
+ val options =
+ listOf(
+ ScreenShareOption(
+ mode = SINGLE_APP,
+ spinnerText = R.string.screen_share_permission_dialog_option_single_app,
+ warningText = singleAppWarningText,
+ spinnerDisabledText = singleAppDisabledText,
+ ),
+ ScreenShareOption(
+ mode = ENTIRE_SCREEN,
+ spinnerText = R.string.screen_share_permission_dialog_option_entire_screen,
+ warningText = entireScreenWarningText
+ )
)
- )
+ return if (singleAppOptionDisabled) {
+ // Make sure "Entire screen" is the first option when "Single App" is disabled.
+ options.reversed()
+ } else {
+ options
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index dbd62fe..d9e3e55 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -27,10 +27,10 @@
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
-import javax.inject.Inject
/** Hosts business logic for interacting with the power system. */
@SysUISingleton
@@ -59,18 +59,28 @@
* Whether we're awake (screen is on and responding to user touch) or asleep (screen is off, or
* on AOD).
*/
- val isAwake = repository.wakefulness
+ val isAwake =
+ repository.wakefulness
.map { it.isAwake() }
.distinctUntilChanged(checkEquivalentUnlessEmitDuplicatesUnderTest)
- /**
- * Helper flow in case "isAsleep" reads better than "!isAwake".
- */
+ /** Helper flow in case "isAsleep" reads better than "!isAwake". */
val isAsleep = isAwake.map { !it }
val screenPowerState = repository.screenPowerState
/**
+ * Notifies the power interactor that a user touch happened.
+ *
+ * @param noChangeLights If true, does not cause the keyboard backlight to turn on because of
+ * this event. This is set when the power key is pressed. We want the device to stay on while
+ * the button is down, but we're about to turn off the screen so we don't want the keyboard
+ * backlight to turn on again. Otherwise the lights flash on and then off and it looks weird.
+ */
+ fun onUserTouch(noChangeLights: Boolean = false) =
+ repository.userTouch(noChangeLights = noChangeLights)
+
+ /**
* Wakes up the device if the device was dozing.
*
* @param why a string explaining why we're waking the device for debugging purposes. Should be
@@ -92,9 +102,7 @@
*/
fun wakeUpForFullScreenIntent() {
if (repository.wakefulness.value.isAsleep() || statusBarStateController.isDozing) {
- repository.wakeUp(
- why = FSI_WAKE_WHY,
- wakeReason = PowerManager.WAKE_REASON_APPLICATION)
+ repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION)
}
}
@@ -120,8 +128,8 @@
* directly.
*/
fun onStartedWakingUp(
- @PowerManager.WakeReason reason: Int,
- powerButtonLaunchGestureTriggeredOnWakeUp: Boolean,
+ @PowerManager.WakeReason reason: Int,
+ powerButtonLaunchGestureTriggeredOnWakeUp: Boolean,
) {
// If the launch gesture was previously detected, either via onCameraLaunchGestureDetected
// or onFinishedGoingToSleep(), carry that state forward. It will be reset by the next
@@ -210,14 +218,14 @@
* reset that flag and then return false.
*/
private val checkEquivalentUnlessEmitDuplicatesUnderTest: (Boolean, Boolean) -> Boolean =
- { old, new ->
- if (emitDuplicateWakefulnessValue) {
- emitDuplicateWakefulnessValue = false
- false
- } else {
- old == new
- }
+ { old, new ->
+ if (emitDuplicateWakefulnessValue) {
+ emitDuplicateWakefulnessValue = false
+ false
+ } else {
+ old == new
}
+ }
/**
* Helper method for tests to simulate the device waking up.
@@ -232,14 +240,14 @@
*/
@JvmOverloads
fun PowerInteractor.setAwakeForTest(
- @PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN,
- forceEmit: Boolean = false
+ @PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN,
+ forceEmit: Boolean = false
) {
emitDuplicateWakefulnessValue = forceEmit
this.onStartedWakingUp(
- reason = reason,
- powerButtonLaunchGestureTriggeredOnWakeUp = false,
+ reason = reason,
+ powerButtonLaunchGestureTriggeredOnWakeUp = false,
)
this.onFinishedWakingUp()
}
@@ -258,15 +266,14 @@
*/
@JvmOverloads
fun PowerInteractor.setAsleepForTest(
- @PowerManager.GoToSleepReason sleepReason: Int =
- PowerManager.GO_TO_SLEEP_REASON_MIN,
- forceEmit: Boolean = false,
+ @PowerManager.GoToSleepReason sleepReason: Int = PowerManager.GO_TO_SLEEP_REASON_MIN,
+ forceEmit: Boolean = false,
) {
emitDuplicateWakefulnessValue = forceEmit
this.onStartedGoingToSleep(reason = sleepReason)
this.onFinishedGoingToSleep(
- powerButtonLaunchGestureTriggeredDuringSleep = false,
+ powerButtonLaunchGestureTriggeredDuringSleep = false,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index b0f54ee..0644237 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -28,8 +28,8 @@
import android.widget.FrameLayout;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.res.R;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.util.LargeScreenUtils;
@@ -59,6 +59,8 @@
private boolean mClippingEnabled;
private boolean mIsFullWidth;
+ private boolean mSceneContainerEnabled;
+
public QSContainerImpl(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -72,6 +74,10 @@
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ }
+
@Override
public boolean hasOverlappingRendering() {
return false;
@@ -161,7 +167,7 @@
}
mQSPanelContainer.setPaddingRelative(
mQSPanelContainer.getPaddingStart(),
- topPadding,
+ mSceneContainerEnabled ? 0 : topPadding,
mQSPanelContainer.getPaddingEnd(),
mQSPanelContainer.getPaddingBottom());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 73a5faa..7b001c7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -24,6 +24,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -44,6 +45,7 @@
mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController);
}
};
+ private final boolean mSceneContainerEnabled;
private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() {
@Override
@@ -64,18 +66,21 @@
QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
ConfigurationController configurationController,
- FalsingManager falsingManager) {
+ FalsingManager falsingManager,
+ SceneContainerFlags sceneContainerFlags) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
mFalsingManager = falsingManager;
mQSPanelContainer = mView.getQSPanelContainer();
+ mSceneContainerEnabled = sceneContainerFlags.isEnabled();
}
@Override
public void onInit() {
mQuickStatusBarHeaderController.init();
+ mView.setSceneContainerEnabled(mSceneContainerEnabled);
}
public void setListening(boolean listening) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index fab7e95..7f91fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -97,6 +97,7 @@
private boolean mStackScrollerOverscrolling;
private QSAnimator mQSAnimator;
+ @Nullable
private HeightListener mPanelView;
private QSSquishinessController mQSSquishinessController;
protected QuickStatusBarHeader mHeader;
@@ -340,6 +341,7 @@
}
if (mQSCustomizerController != null) {
mQSCustomizerController.setQs(null);
+ mQSCustomizerController.setContainerController(null);
}
mScrollListener = null;
if (mContainer != null) {
@@ -347,6 +349,10 @@
}
mDumpManager.unregisterDumpable(getClass().getSimpleName());
mListeningAndVisibilityLifecycleOwner.destroy();
+ ViewGroup parent = ((ViewGroup) getView().getParent());
+ if (parent != null) {
+ parent.removeView(getView());
+ }
}
public void onSaveInstanceState(Bundle outState) {
@@ -853,6 +859,10 @@
mQSCustomizerController.hide();
}
+ public void closeCustomizerImmediately() {
+ mQSCustomizerController.hide(false);
+ }
+
public void notifyCustomizeChanged() {
// The customize state changed, so our height changed.
mContainer.updateExpansion();
@@ -863,7 +873,9 @@
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
- mPanelView.onQsHeightChanged();
+ if (mPanelView != null) {
+ mPanelView.onQsHeightChanged();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 3227f75..ddd7d67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -39,9 +39,9 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.RemeasuringLinearLayout;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -110,6 +110,8 @@
*/
private boolean mCanCollapse = true;
+ private boolean mSceneContainerEnabled;
+
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -153,6 +155,13 @@
}
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ if (mSceneContainerEnabled) {
+ updatePadding();
+ }
+ }
+
protected void setHorizontalContentContainerClipping() {
mHorizontalContentContainer.setClipChildren(true);
mHorizontalContentContainer.setClipToPadding(false);
@@ -375,7 +384,7 @@
int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
setPaddingRelative(getPaddingStart(),
- paddingTop,
+ mSceneContainerEnabled ? 0 : paddingTop,
getPaddingEnd(),
paddingBottom);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 6bbdc54..5eb9620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -34,6 +34,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -61,6 +62,8 @@
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mListening;
+ private final boolean mSceneContainerEnabled;
+
private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
@@ -82,7 +85,8 @@
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- SplitShadeStateController splitShadeStateController) {
+ SplitShadeStateController splitShadeStateController,
+ SceneContainerFlags sceneContainerFlags) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
mTunerService = tunerService;
@@ -96,6 +100,7 @@
mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController);
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mSceneContainerEnabled = sceneContainerFlags.isEnabled();
}
@Override
@@ -116,6 +121,7 @@
mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
mView.updateResources();
+ mView.setSceneContainerEnabled(mSceneContainerEnabled);
if (mView.isListening()) {
refreshAllTiles();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 67c4208..c657b55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -40,6 +40,8 @@
protected QuickQSPanel mHeaderQsPanel;
+ private boolean mSceneContainerEnabled;
+
public QuickStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -52,6 +54,13 @@
updateResources();
}
+ void setSceneContainerEnabled(boolean enabled) {
+ mSceneContainerEnabled = enabled;
+ if (mSceneContainerEnabled) {
+ updateResources();
+ }
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -82,7 +91,9 @@
setLayoutParams(lp);
MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams();
- if (largeScreenHeaderActive) {
+ if (mSceneContainerEnabled) {
+ qqsLP.topMargin = 0;
+ } else if (largeScreenHeaderActive) {
qqsLP.topMargin = mContext.getResources()
.getDimensionPixelSize(R.dimen.qqs_layout_margin_top);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 64960e6..1d92d78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -29,17 +30,20 @@
private final QuickQSPanelController mQuickQSPanelController;
private boolean mListening;
+ private final boolean mSceneContainerEnabled;
@Inject
QuickStatusBarHeaderController(QuickStatusBarHeader view,
- QuickQSPanelController quickQSPanelController
+ QuickQSPanelController quickQSPanelController,
+ SceneContainerFlags sceneContainerFlags
) {
super(view);
mQuickQSPanelController = quickQSPanelController;
+ mSceneContainerEnabled = sceneContainerFlags.isEnabled();
}
-
@Override
protected void onViewAttached() {
+ mView.setSceneContainerEnabled(mSceneContainerEnabled);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 024e760..c28371c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -244,7 +244,12 @@
/** Hice the customizer. */
public void hide() {
- final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+ hide(true);
+ }
+
+ public void hide(boolean animated) {
+ final boolean animate = animated
+ && mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
if (mView.isShown()) {
mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
mToolbar.dismissPopupMenus();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index bd4c6e1..1aef920 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -32,6 +32,8 @@
import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.di.QSTilesModule;
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter;
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl;
import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
@@ -42,18 +44,19 @@
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
-
import java.util.Map;
import javax.inject.Named;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.Multibinds;
+
/**
* Module for QS dependencies
*/
-@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class},
+@Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class},
includes = {
MediaModule.class,
QSExternalModule.class,
@@ -110,4 +113,7 @@
manager.init();
return manager;
}
+
+ @Binds
+ QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
index ba1aa62..b942368 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt
@@ -21,12 +21,12 @@
import dagger.BindsInstance
import dagger.Subcomponent
-@Subcomponent(modules = [QSFlexiglassModule::class])
+@Subcomponent(modules = [QSSceneModule::class])
@QSScope
-interface QSFlexiglassComponent : QSComponent {
+interface QSSceneComponent : QSComponent {
@Subcomponent.Factory
interface Factory {
- fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent
+ fun create(@RootView @BindsInstance rootView: View): QSSceneComponent
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
rename to packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
index 36fac44..446cb62 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt
@@ -24,7 +24,7 @@
import javax.inject.Named
@Module(includes = [QSScopeModule::class])
-interface QSFlexiglassModule {
+interface QSSceneModule {
@Module
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
new file mode 100644
index 0000000..b4340f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.qs.ui.adapter
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.VisibleForTesting
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.QSImpl
+import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+// TODO(307945185) Split View concerns into a ViewBinder
+/** Adapter to use between Scene system and [QSImpl] */
+interface QSSceneAdapter {
+ /** Whether [QSImpl] is currently customizing */
+ val isCustomizing: StateFlow<Boolean>
+
+ /**
+ * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by
+ * the interactor.
+ */
+ val qsView: Flow<View>
+
+ /**
+ * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
+ * [qsView]
+ */
+ suspend fun inflate(context: Context, parent: ViewGroup? = null)
+
+ /** Set the current state for QS. [state] must not be [State.INITIAL]. */
+ fun setState(state: State)
+
+ sealed class State(
+ val isVisible: Boolean,
+ val expansion: Float,
+ ) {
+ data object CLOSED : State(false, 0f)
+ data object QQS : State(true, 0f)
+ data object QS : State(true, 1f)
+ }
+}
+
+@SysUISingleton
+class QSSceneAdapterImpl
+@VisibleForTesting
+constructor(
+ private val qsSceneComponentFactory: QSSceneComponent.Factory,
+ private val qsImplProvider: Provider<QSImpl>,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Application applicationScope: CoroutineScope,
+ private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater,
+) : QSContainerController, QSSceneAdapter {
+
+ @Inject
+ constructor(
+ qsSceneComponentFactory: QSSceneComponent.Factory,
+ qsImplProvider: Provider<QSImpl>,
+ @Main dispatcher: CoroutineDispatcher,
+ @Application scope: CoroutineScope,
+ ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater)
+
+ private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED)
+ private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isCustomizing = _isCustomizing.asStateFlow()
+
+ private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null)
+ val qsImpl = _qsImpl.asStateFlow()
+ override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull()
+
+ init {
+ applicationScope.launch {
+ state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
+ _qsImpl.value?.apply {
+ if (state != QSSceneAdapter.State.QS && customizing) {
+ this@apply.closeCustomizerImmediately()
+ }
+ applyState(state)
+ }
+ }
+ }
+ }
+
+ override fun setCustomizerAnimating(animating: Boolean) {}
+
+ override fun setCustomizerShowing(showing: Boolean) {
+ _isCustomizing.value = showing
+ }
+
+ override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) {
+ setCustomizerShowing(showing)
+ }
+
+ override fun setDetailShowing(showing: Boolean) {}
+
+ override suspend fun inflate(context: Context, parent: ViewGroup?) {
+ withContext(mainDispatcher) {
+ val inflater = asyncLayoutInflaterFactory(context)
+ val view = suspendCoroutine { continuation ->
+ inflater.inflate(R.layout.qs_panel, parent) { view, _, _ ->
+ continuation.resume(view)
+ }
+ }
+ val bundle = Bundle()
+ _qsImpl.value?.onSaveInstanceState(bundle)
+ _qsImpl.value?.onDestroy()
+ val component = qsSceneComponentFactory.create(view)
+ val qs = qsImplProvider.get()
+ qs.onCreate(null)
+ qs.onComponentCreated(component, bundle)
+ _qsImpl.value = qs
+ qs.view.setPadding(0, 0, 0, 0)
+ qs.setContainerController(this@QSSceneAdapterImpl)
+ qs.applyState(state.value)
+ }
+ }
+ override fun setState(state: QSSceneAdapter.State) {
+ this.state.value = state
+ }
+
+ private fun QSImpl.applyState(state: QSSceneAdapter.State) {
+ setQsVisible(state.isVisible)
+ setExpanded(state.isVisible)
+ setListening(state.isVisible)
+ setQsExpansion(state.expansion, 1f, 0f, 1f)
+ setTransitionToFullShadeProgress(false, 1f, 1f)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 5993cf1..3941fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -18,8 +18,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import javax.inject.Inject
+import kotlinx.coroutines.flow.map
/** Models UI state and handles user input for the quick settings scene. */
@SysUISingleton
@@ -28,7 +34,20 @@
constructor(
private val deviceEntryInteractor: DeviceEntryInteractor,
val shadeHeaderViewModel: ShadeHeaderViewModel,
+ val qsSceneAdapter: QSSceneAdapter,
) {
/** Notifies that some content in quick settings was clicked. */
fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry()
+
+ val destinationScenes =
+ qsSceneAdapter.isCustomizing.map { customizing ->
+ if (customizing) {
+ mapOf<UserAction, SceneModel>(UserAction.Back to SceneModel(SceneKey.QuickSettings))
+ } else {
+ mapOf(
+ UserAction.Back to SceneModel(SceneKey.Shade),
+ UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index b144003..0abde4d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -18,7 +18,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -48,7 +48,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
- private val powerRepository: PowerRepository,
+ private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
) {
@@ -202,7 +202,7 @@
/** Handles a user input event. */
fun onUserInput() {
- powerRepository.userTouch()
+ powerInteractor.onUserTouch()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index ca2828b..8def457 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,7 +19,10 @@
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
@@ -72,6 +75,8 @@
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val powerInteractor: PowerInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
) : CoreStartable {
override fun start() {
@@ -132,6 +137,33 @@
}
}
applicationScope.launch {
+ simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked ->
+ val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
+ val isUnlocked = deviceEntryInteractor.isUnlocked.value
+
+ when {
+ isAnySimLocked -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Bouncer,
+ loggingReason = "Need to authenticate locked sim card."
+ )
+ }
+ isUnlocked && !canSwipeToEnter -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason = "Sim cards are unlocked."
+ )
+ }
+ else -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Lockscreen,
+ loggingReason = "Sim cards are unlocked."
+ )
+ }
+ }
+ }
+ }
+ applicationScope.launch {
deviceEntryInteractor.isUnlocked
.mapNotNull { isUnlocked ->
val renderedScenes =
@@ -206,6 +238,14 @@
"device is waking up while unlocked without the ability" +
" to swipe up on lockscreen to enter.",
)
+ } else if (
+ authenticationInteractor.getAuthenticationMethod() ==
+ AuthenticationMethodModel.Sim
+ ) {
+ switchToScene(
+ targetSceneKey = SceneKey.Bouncer,
+ loggingReason = "device is starting to wake up with a locked sim"
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index e40d2b7..d14ef35 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -17,8 +17,8 @@
package com.android.systemui.scene.shared.flag
import androidx.annotation.VisibleForTesting
-import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.sceneContainer
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +28,7 @@
import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.ResourceBooleanFlag
import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import dagger.Module
import dagger.Provides
import dagger.assisted.Assisted
@@ -58,8 +59,6 @@
@VisibleForTesting
val classicFlagTokens: List<Flag<Boolean>> =
listOf(
- Flags.MIGRATE_NSSL,
- Flags.MIGRATE_KEYGUARD_STATUS_VIEW,
Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW,
)
}
@@ -75,6 +74,10 @@
flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
flagValue = keyguardBottomAreaRefactor(),
),
+ AconfigFlagMustBeEnabled(
+ flagName = KeyguardShadeMigrationNssl.FLAG_NAME,
+ flagValue = KeyguardShadeMigrationNssl.isEnabled,
+ ),
) +
classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index ead10d6..9f4ea27 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -181,12 +181,16 @@
if (mCancelTimeoutRunnable != null) {
mCancelTimeoutRunnable.run();
}
- finish();
+ requestFinish();
}
return super.onKeyDown(keyCode, event);
}
+ protected void requestFinish() {
+ finish();
+ }
+
private boolean triggeredByBrightnessKey() {
return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false);
}
@@ -197,6 +201,6 @@
}
final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS,
AccessibilityManager.FLAG_CONTENT_CONTROLS);
- mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout);
+ mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::requestFinish, timeout);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e9c930a..a44b4b4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -29,7 +29,6 @@
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -41,7 +40,6 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
-import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -64,7 +62,6 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.Process;
import android.os.Trace;
import android.os.UserManager;
import android.os.VibrationEffect;
@@ -137,6 +134,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
@@ -357,6 +356,7 @@
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final QuickSettingsController mQsController;
+ private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
private long mDownTime;
@@ -409,6 +409,7 @@
private float mOverStretchAmount;
private float mDownX;
private float mDownY;
+ private boolean mIsTrackpadReverseScroll;
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
private int mDisplayLeftInset = 0; // in pixels
@@ -777,7 +778,8 @@
KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
SplitShadeStateController splitShadeStateController,
PowerInteractor powerInteractor,
- KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {
+ KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
+ NaturalScrollingSettingObserver naturalScrollingSettingObserver) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardFadingAwayChanged() {
@@ -806,6 +808,7 @@
mPowerInteractor = powerInteractor;
mKeyguardViewConfigurator = keyguardViewConfigurator;
mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
+ mNaturalScrollingSettingObserver = naturalScrollingSettingObserver;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1265,7 +1268,7 @@
mKeyguardStatusViewController.onDestroy();
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
// Need a shared controller until mKeyguardStatusViewController can be removed from
// here, due to important state being set in that controller. Rebind in order to pick
// up config changes
@@ -1318,7 +1321,7 @@
// Reset any left over overscroll state. It is a rare corner case but can happen.
mQsController.setOverScrollAmount(0);
mScrimController.setNotificationsOverScrollAmount(0);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mNotificationStackScrollLayoutController.setOverExpansion(0);
mNotificationStackScrollLayoutController.setOverScrollAmount(0);
}
@@ -1339,7 +1342,7 @@
}
updateClockAppearance();
mQsController.updateQsState();
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mNotificationStackScrollLayoutController.updateFooter();
}
}
@@ -1371,7 +1374,7 @@
void reInflateViews() {
debugLog("reInflateViews");
// Re-inflate the status view group.
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
KeyguardStatusView keyguardStatusView =
mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
@@ -1483,7 +1486,7 @@
}
private void updateMaxDisplayedNotifications(boolean recompute) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
return;
}
@@ -1640,7 +1643,7 @@
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.setLockscreenClockY(
mClockPositionAlgorithm.getExpandedPreferredClockY());
}
@@ -1654,7 +1657,7 @@
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX, mClockPositionResult.clockY,
mClockPositionResult.clockScale, animateClock);
@@ -1727,7 +1730,7 @@
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
ConstraintLayout layout;
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
layout = mKeyguardViewConfigurator.getKeyguardRootView();
} else {
layout = mNotificationContainerParent;
@@ -1902,7 +1905,7 @@
}
float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
mKeyguardStatusViewController.setAlpha(alpha);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
// TODO (b/296373478) This is for split shade media movement.
} else {
mKeyguardStatusViewController
@@ -2482,7 +2485,7 @@
void requestScrollerTopPaddingUpdate(boolean animate) {
float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
getKeyguardNotificationStaticPadding(), mExpandedFraction);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
mSharedNotificationContainerInteractor.setTopPosition(padding);
} else {
mNotificationStackScrollLayoutController.updateTopPadding(padding, animate);
@@ -2840,16 +2843,7 @@
}
if (!mStatusBarStateController.isDozing()) {
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
- } else {
- mVibratorHelper.vibrate(
- Process.myUid(),
- mView.getContext().getPackageName(),
- ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
- "falsing-additional-tap-required",
- TOUCH_VIBRATION_ATTRIBUTES);
- }
+ mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
}
}
@@ -2949,7 +2943,7 @@
@Override
public void onScreenTurningOn() {
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.dozeTimeTick();
}
}
@@ -3201,7 +3195,7 @@
public void dozeTimeTick() {
mLockIconViewController.dozeTimeTick();
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.dozeTimeTick();
}
if (mInterpolatedDarkAmount > 0) {
@@ -3677,14 +3671,10 @@
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
if (!openingWithTouch || !mHasVibratedOnOpen) {
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mVibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_START
- );
- } else {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
+ mVibratorHelper.performHapticFeedback(
+ mView,
+ HapticFeedbackConstants.GESTURE_START
+ );
mHasVibratedOnOpen = true;
mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
}
@@ -3697,7 +3687,7 @@
*/
private boolean isDirectionUpwards(float x, float y) {
float xDiff = x - mInitialExpandX;
- float yDiff = y - mInitialExpandY;
+ float yDiff = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY);
if (yDiff >= 0) {
return false;
}
@@ -3734,7 +3724,7 @@
|| (!isFullyExpanded() && !isFullyCollapsed())
|| event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
mVelocityTracker.computeCurrentVelocity(1000);
- float vel = mVelocityTracker.getYVelocity();
+ float vel = (mIsTrackpadReverseScroll ? -1 : 1) * mVelocityTracker.getYVelocity();
float vectorVel = (float) Math.hypot(
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -3773,8 +3763,9 @@
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK);
}
+ float dy = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY);
@Classifier.InteractionType int interactionType = vel == 0 ? GENERIC
- : y - mInitialExpandY > 0 ? QUICK_SETTINGS
+ : dy > 0 ? QUICK_SETTINGS
: (mKeyguardStateController.canDismissLockScreen()
? UNLOCK : BOUNCER_UNLOCK);
@@ -3801,7 +3792,7 @@
private float getCurrentExpandVelocity() {
mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getYVelocity();
+ return (mIsTrackpadReverseScroll ? -1 : 1) * mVelocityTracker.getYVelocity();
}
private void endClosing() {
@@ -4427,7 +4418,7 @@
&& statusBarState == KEYGUARD) {
// This means we're doing the screen off animation - position the keyguard status
// view where it'll be on AOD, so we can animate it in.
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.updatePosition(
mClockPositionResult.clockX,
mClockPositionResult.clockYFullyDozing,
@@ -4547,7 +4538,7 @@
setDozing(true /* dozing */, false /* animate */);
mStatusBarStateController.setUpcomingState(KEYGUARD);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
mStatusBarStateController.setState(KEYGUARD);
} else {
mStatusBarStateListener.onStateChanged(KEYGUARD);
@@ -4608,7 +4599,7 @@
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
// Update Clock Pivot (used by anti-burnin transformations)
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
}
@@ -4718,7 +4709,7 @@
private Consumer<Float> setTransitionY(
NotificationStackScrollLayoutController stackScroller) {
return (Float translationY) -> {
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mKeyguardStatusViewController.setTranslationY(translationY,
/* excludeMedia= */false);
stackScroller.setTranslationY(translationY);
@@ -4760,7 +4751,7 @@
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+ if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
return false;
}
@@ -4842,6 +4833,10 @@
+ " mAnimatingOnDown: true, mClosing: true");
return true;
}
+
+ mIsTrackpadReverseScroll =
+ !mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()
+ && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event);
if (!isTracking() || isFullyCollapsed()) {
mInitialExpandY = y;
mInitialExpandX = x;
@@ -4884,7 +4879,7 @@
}
break;
case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialExpandY;
+ final float h = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY);
addMovement(event);
final boolean openShadeWithoutHun =
mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
@@ -4926,7 +4921,7 @@
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+ if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) {
return false;
}
@@ -5148,7 +5143,7 @@
if (!isFullyCollapsed()) {
maybeVibrateOnOpening(true /* openingWithTouch */);
}
- float h = y - mInitialExpandY;
+ float h = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY);
// If the panel was collapsed when touching, we only need to check for the
// y-component of the gesture, as we have no conflicting horizontal gesture.
@@ -5197,6 +5192,7 @@
mQsController.cancelJankMonitoring();
}
}
+ mIsTrackpadReverseScroll = false;
break;
}
return !mGestureWaitForTouchSlop || isTracking();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index a2ca49d..d0f2784 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -53,6 +53,7 @@
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
@@ -457,7 +458,7 @@
&& !bouncerShowing
&& !mStatusBarStateController.isDozing()) {
if (mDragDownHelper.isDragDownEnabled()) {
- if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
// When on lockscreen, if the touch originates at the top of the screen
// go directly to QS and not the shade
if (mQuickSettingsController.shouldQuickSettingsIntercept(
@@ -469,7 +470,7 @@
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
if (result) {
mLastInterceptWasDragDownHelper = true;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -501,7 +502,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
@@ -516,7 +517,7 @@
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
return true;
}
- if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -602,7 +603,7 @@
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 866cfb4..9c8a286 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentService
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.plugins.qs.QS
@@ -129,7 +130,6 @@
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
mView.setStackScroller(notificationStackScrollLayoutController.getView())
- mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){
mView.enableGraphOptimization()
}
@@ -283,7 +283,7 @@
}
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
- if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (KeyguardShadeMigrationNssl.isEnabled) {
return
}
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index af44c4e..de3d16a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -32,6 +32,7 @@
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.res.R;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.plugins.qs.QS;
@@ -59,7 +60,6 @@
private QS mQs;
private View mQSContainer;
private int mLastQSPaddingBottom;
- private boolean mIsMigratingNSSL;
/**
* These are used to compute the bounding box containing the shade and the notification scrim,
@@ -180,10 +180,6 @@
super.dispatchDraw(canvas);
}
- void setMigratingNSSL(boolean isMigrating) {
- mIsMigratingNSSL = isMigrating;
- }
-
void enableGraphOptimization() {
setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
}
@@ -196,7 +192,7 @@
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (mIsMigratingNSSL) {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
return super.drawChild(canvas, child, drawingTime);
}
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index c8b6a2e..bbcf10b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -6,8 +6,12 @@
per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
-per-file *ShadeHeader* = kozynski@google.com, asc@google.com
-per-file *Shade* = justinweir@google.com
+per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com
+
+per-file *Interactor* = set noparent
+per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Repository* = set noparent
+per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index d73fa14..335e65e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -68,10 +68,9 @@
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -107,12 +106,12 @@
import dalvik.annotation.optimization.NeverCompile;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import javax.inject.Inject;
+import dagger.Lazy;
+
/** Handles QuickSettings touch handling, expansion and animation state
* TODO (b/264460656) make this dumpable
*/
@@ -155,7 +154,6 @@
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final CastController mCastController;
private final SplitShadeStateController mSplitShadeStateController;
- private final FeatureFlags mFeatureFlags;
private final InteractionJankMonitor mInteractionJankMonitor;
private final ShadeRepository mShadeRepository;
private final ShadeInteractor mShadeInteractor;
@@ -209,12 +207,6 @@
/** Indicates QS is at its max height */
private boolean mFullyExpanded;
- /**
- * Determines if QS should be already expanded when expanding shade.
- * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
- * It needs to be set when movement starts as it resets at the end of expansion/collapse.
- */
- private boolean mExpandImmediate;
private boolean mExpandedWhenExpandingStarted;
private boolean mAnimatingHiddenFromCollapsed;
private boolean mVisible;
@@ -333,7 +325,6 @@
AccessibilityManager accessibilityManager,
LockscreenGestureLogger lockscreenGestureLogger,
MetricsLogger metricsLogger,
- FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
ShadeLogger shadeLog,
DumpManager dumpManager,
@@ -384,7 +375,6 @@
mShadeLog = shadeLog;
mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mCastController = castController;
- mFeatureFlags = featureFlags;
mInteractionJankMonitor = interactionJankMonitor;
mShadeRepository = shadeRepository;
mShadeInteractor = shadeInteractor;
@@ -516,7 +506,7 @@
/** */
@VisibleForTesting
boolean isExpandImmediate() {
- return mExpandImmediate;
+ return mShadeRepository.getLegacyExpandImmediate().getValue();
}
float getInitialTouchY() {
@@ -606,7 +596,7 @@
// close the whole shade with one motion. Also this will be always true when closing
// split shade as there QS are always expanded so every collapsing motion is motion from
// expanded QS to closed panel
- return mExpandImmediate || (getExpanded()
+ return isExpandImmediate() || (getExpanded()
&& !isTracking() && !isExpansionAnimating()
&& !mExpansionFromOverscroll);
}
@@ -724,7 +714,9 @@
/** Closes the Qs customizer. */
public void closeQsCustomizer() {
- mQs.closeCustomizer();
+ if (mQs != null) {
+ mQs.closeCustomizer();
+ }
}
/** Returns whether touches from the notification panel should be disallowed */
@@ -794,7 +786,7 @@
&& mBarState == SHADE) {
Log.wtf(TAG,
"setting QS height to 0 in split shade while shade is open(ing). "
- + "Value of mExpandImmediate = " + mExpandImmediate);
+ + "Value of isExpandImmediate() = " + isExpandImmediate());
}
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
@@ -943,10 +935,9 @@
}
void setExpandImmediate(boolean expandImmediate) {
- if (expandImmediate != mExpandImmediate) {
+ if (expandImmediate != isExpandImmediate()) {
mShadeLog.logQsExpandImmediateChanged(expandImmediate);
- mExpandImmediate = expandImmediate;
- mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
+ mShadeRepository.setLegacyExpandImmediate(expandImmediate);
}
}
@@ -982,6 +973,7 @@
void updateQsState() {
boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled;
+ mShadeRepository.setLegacyQsFullscreen(qsFullScreen);
mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
mNotificationStackScrollLayoutController.setScrollingEnabled(
mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
@@ -998,7 +990,7 @@
public void updateExpansion() {
if (mQs == null) return;
final float squishiness;
- if ((mExpandImmediate || getExpanded()) && !mSplitShadeEnabled) {
+ if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) {
squishiness = 1;
} else if (mTransitioningToFullShadeProgress > 0.0f) {
squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
@@ -1776,7 +1768,7 @@
// Dragging down on the lockscreen statusbar should prohibit other interactions
// immediately, otherwise we'll wait on the touchslop. This is to allow
// dragging down to expanded quick settings directly on the lockscreen.
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
}
}
@@ -1821,7 +1813,7 @@
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(
mInitialTouchX, mInitialTouchY, h)) {
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
}
mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
@@ -2079,8 +2071,8 @@
ipw.println(getExpanded());
ipw.print("mFullyExpanded=");
ipw.println(mFullyExpanded);
- ipw.print("mExpandImmediate=");
- ipw.println(mExpandImmediate);
+ ipw.print("isExpandImmediate()=");
+ ipw.println(isExpandImmediate());
ipw.print("mExpandedWhenExpandingStarted=");
ipw.println(mExpandedWhenExpandingStarted);
ipw.print("mAnimatingHiddenFromCollapsed=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index 7a803867..53eccfd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl
import dagger.Binds
import dagger.Module
@@ -30,4 +32,8 @@
@Binds
@SysUISingleton
abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindsShadeInteractor(si: ShadeInteractorEmptyImpl): ShadeInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index fca98f5..e20534c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -169,12 +169,6 @@
}
}
- fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
- for (cb in shadeStateEventsListeners) {
- cb.onExpandImmediateChanged(expandImmediateEnabled)
- }
- }
-
private fun debugLog(msg: String) {
if (!DEBUG) return
Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 89aaaaf..54467cf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,13 +17,40 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
-
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
+import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import javax.inject.Provider
/** Module for classes related to the notification shade. */
@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
abstract class ShadeModule {
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideBaseShadeInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
+ sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
+ ): BaseShadeInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+ }
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindsShadeInteractor(si: ShadeInteractorImpl): ShadeInteractor
+
@Binds
@SysUISingleton
abstract fun bindsShadeViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 5804040..c8511d7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -35,16 +35,5 @@
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
-
- /**
- * Invoked when the "expand immediate" attribute changes.
- *
- * An example of expanding immediately is when swiping down from the top with two fingers.
- * Instead of going to QQS, we immediately expand to full QS.
- *
- * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
- * going to QQS, the panel fully collapses.
- */
- fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 8bab669..47b08fe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -93,6 +93,29 @@
*/
@Deprecated("Use ShadeInteractor instead") val legacyIsQsExpanded: StateFlow<Boolean>
+ /**
+ * QuickSettingsController.mExpandImmediate as a flow. Indicates that Quick Settings is being
+ * expanded without first expanding the Shade or Quick Settings is being collapsed without first
+ * collapsing to shade, i.e. expanding with 2-finger swipe or collapsing by flinging from the
+ * bottom of the screen. Replaced by ShadeInteractor.isQsBypassingShade.
+ */
+ @Deprecated("Use ShadeInteractor.isQsBypassingShade instead")
+ val legacyExpandImmediate: StateFlow<Boolean>
+
+ /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */
+ @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean>
+
+ /** */
+ @Deprecated("Use ShadeInteractor instead")
+ fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean)
+
+ /**
+ * Sets whether Quick Settings is being expanded without first expanding the Shade or Quick
+ * Settings is being collapsed without first collapsing to shade.
+ */
+ @Deprecated("Use ShadeInteractor instead")
+ fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean)
+
/** Sets whether QS is expanded. */
@Deprecated("Use ShadeInteractor instead")
fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean)
@@ -105,12 +128,13 @@
fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean)
/** Sets whether the user is moving Quick Settings with a pointer */
- fun setLegacyQsTracking(legacyQsTracking: Boolean)
+ @Deprecated("Use ShadeInteractor instead") fun setLegacyQsTracking(legacyQsTracking: Boolean)
/** Sets whether the user is moving the shade with a pointer */
- fun setLegacyShadeTracking(tracking: Boolean)
+ @Deprecated("Use ShadeInteractor instead") fun setLegacyShadeTracking(tracking: Boolean)
/** Sets whether the user is moving the shade with a pointer, on lockscreen only */
+ @Deprecated("Use ShadeInteractor instead")
fun setLegacyLockscreenShadeTracking(tracking: Boolean)
/** Amount shade has expanded with regard to the UDFPS location */
@@ -199,6 +223,22 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyIsQsExpanded: StateFlow<Boolean> = _legacyIsQsExpanded.asStateFlow()
+ private val _legacyExpandImmediate = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyExpandImmediate: StateFlow<Boolean> = _legacyExpandImmediate.asStateFlow()
+
+ private val _legacyQsFullscreen = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow()
+
+ override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
+ _legacyQsFullscreen.value = legacyQsFullscreen
+ }
+
+ override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
+ _legacyExpandImmediate.value = legacyExpandImmediate
+ }
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
_legacyIsQsExpanded.value = legacyIsQsExpanded
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index d687ef6..6a9757f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -16,149 +16,42 @@
package com.android.systemui.shade.domain.interactor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
-import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
-import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
-import javax.inject.Inject
-import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.currentCoroutineContext
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.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.isActive
/** Business logic for shade interactions. */
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class ShadeInteractor
-@Inject
-constructor(
- @Application scope: CoroutineScope,
- deviceProvisioningRepository: DeviceProvisioningRepository,
- disableFlagsRepository: DisableFlagsRepository,
- dozeParams: DozeParameters,
- sceneContainerFlags: SceneContainerFlags,
- // TODO(b/300258424) convert to direct reference instead of provider
- sceneInteractorProvider: Provider<SceneInteractor>,
- keyguardRepository: KeyguardRepository,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
- powerInteractor: PowerInteractor,
- userSetupRepository: UserSetupRepository,
- userSwitcherInteractor: UserSwitcherInteractor,
- sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
- private val repository: ShadeRepository,
-) {
+interface ShadeInteractor : BaseShadeInteractor {
/** Emits true if the shade is currently allowed and false otherwise. */
- val isShadeEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
- .map { it.isShadeEnabled() }
- .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
-
- /**
- * Whether split shade, the combined notifications and quick settings shade used for large
- * screens, is enabled.
- */
- val isSplitShadeEnabled: Flow<Boolean> =
- sharedNotificationContainerInteractor.configurationBasedDimensions
- .map { dimens -> dimens.useSplitShade }
- .distinctUntilChanged()
-
- /** The amount [0-1] that the shade has been opened */
- val shadeExpansion: Flow<Float> =
- if (sceneContainerFlags.isEnabled()) {
- sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.Shade)
- } else {
- combine(
- repository.lockscreenShadeExpansion,
- keyguardRepository.statusBarState,
- repository.legacyShadeExpansion,
- repository.qsExpansion,
- isSplitShadeEnabled
- ) {
- lockscreenShadeExpansion,
- statusBarState,
- legacyShadeExpansion,
- qsExpansion,
- splitShadeEnabled ->
- when (statusBarState) {
- // legacyShadeExpansion is 1 instead of 0 when QS is expanded
- StatusBarState.SHADE ->
- if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
- StatusBarState.KEYGUARD -> lockscreenShadeExpansion
- // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
- // the pointer is lifted and the lockscreen shade is fully expanded
- StatusBarState.SHADE_LOCKED -> 1f
- }
- }
- .distinctUntilChanged()
- }
-
- /**
- * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
- * report 0f. If split shade is enabled, value matches shadeExpansion.
- */
- val qsExpansion: StateFlow<Float> =
- if (sceneContainerFlags.isEnabled()) {
- val qsExp = sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
- combine(isSplitShadeEnabled, shadeExpansion, qsExp) {
- isSplitShadeEnabled,
- shadeExp,
- qsExp ->
- if (isSplitShadeEnabled) {
- shadeExp
- } else {
- qsExp
- }
- }
- .stateIn(scope, SharingStarted.Eagerly, 0f)
- } else {
- repository.qsExpansion
- }
-
- /** Whether Quick Settings is expanded a non-zero amount. */
- val isQsExpanded: StateFlow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
- qsExpansion
- .map { it > 0 }
- .distinctUntilChanged()
- .stateIn(scope, SharingStarted.Eagerly, false)
- } else {
- repository.legacyIsQsExpanded
- }
-
- /** The amount [0-1] either QS or the shade has been opened. */
- val anyExpansion: StateFlow<Float> =
- combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
- .stateIn(scope, SharingStarted.Eagerly, 0f)
+ val isShadeEnabled: StateFlow<Boolean>
/** Whether either the shade or QS is fully expanded. */
- val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
+ val isAnyFullyExpanded: Flow<Boolean>
+
+ /** Whether the Shade is fully expanded. */
+ val isShadeFullyExpanded: Flow<Boolean>
+
+ /**
+ * Whether the user is expanding or collapsing either the shade or quick settings with user
+ * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
+ * but a transition they initiated is still animating.
+ */
+ val isUserInteracting: Flow<Boolean>
+
+ /** Are touches allowed on the notification panel? */
+ val isShadeTouchable: Flow<Boolean>
+
+ /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
+ val isExpandToQsEnabled: Flow<Boolean>
+}
+
+/** ShadeInteractor methods with implementations that differ between non-empty impls. */
+interface BaseShadeInteractor {
+ /** The amount [0-1] either QS or the shade has been opened. */
+ val anyExpansion: StateFlow<Float>
/**
* Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
@@ -169,149 +62,53 @@
*
* TODO(b/300258424) remove all but the first sentence of this comment
*/
- val isAnyExpanded: StateFlow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
- anyExpansion.map { it > 0f }.distinctUntilChanged()
- } else {
- repository.legacyExpandedOrAwaitingInputTransfer
- }
- .stateIn(scope, SharingStarted.Eagerly, false)
+ val isAnyExpanded: StateFlow<Boolean>
+
+ /** The amount [0-1] that the shade has been opened. */
+ val shadeExpansion: Flow<Float>
+
+ /**
+ * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
+ * report 0f. If split shade is enabled, value matches shadeExpansion.
+ */
+ val qsExpansion: StateFlow<Float>
+
+ /** Whether Quick Settings is expanded a non-zero amount. */
+ val isQsExpanded: StateFlow<Boolean>
+
+ /**
+ * Emits true whenever Quick Settings is being expanded without first expanding the Shade or if
+ * if Quick Settings is being collapsed without first collapsing to shade, i.e. expanding with
+ * 2-finger swipe or collapsing by flinging from the bottom of the screen. This concept was
+ * previously called "expand immediate" in the legacy codebase.
+ */
+ val isQsBypassingShade: Flow<Boolean>
+
+ /**
+ * Emits true when QS is displayed over the entire screen of the device. Currently, this only
+ * happens on phones that are not unfolded when QS expansion is equal to 1.
+ */
+ val isQsFullscreen: Flow<Boolean>
/**
* Whether the user is expanding or collapsing the shade with user input. This will be true even
* if the user's input gesture has ended but a transition they initiated is animating.
*/
- val isUserInteractingWithShade: Flow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
- sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.Shade)
- } else {
- combine(
- userInteractingFlow(
- repository.legacyShadeTracking,
- repository.legacyShadeExpansion
- ),
- repository.legacyLockscreenShadeTracking
- ) { legacyShadeTracking, legacyLockscreenShadeTracking ->
- legacyShadeTracking || legacyLockscreenShadeTracking
- }
- }
+ val isUserInteractingWithShade: Flow<Boolean>
/**
* Whether the user is expanding or collapsing quick settings with user input. This will be true
* even if the user's input gesture has ended but a transition they initiated is still
* animating.
*/
- val isUserInteractingWithQs: Flow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
- sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.QuickSettings)
- } else {
- userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
- }
+ val isUserInteractingWithQs: Flow<Boolean>
+}
- /**
- * Whether the user is expanding or collapsing either the shade or quick settings with user
- * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
- * but a transition they initiated is still animating.
- */
- val isUserInteracting: Flow<Boolean> =
- combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
- .distinctUntilChanged()
-
- /** Are touches allowed on the notification panel? */
- val isShadeTouchable: Flow<Boolean> =
- combine(
- powerInteractor.isAsleep,
- keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
- keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
- deviceProvisioningRepository.isFactoryResetProtectionActive,
- ) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
- when {
- // Touches are disabled when Factory Reset Protection is active
- isFrpActive -> false
- // If the device is going to sleep, only accept touches if we're still
- // animating
- goingToSleep -> dozeParams.shouldControlScreenOff()
- // If the device is asleep, only accept touches if there's a pulse
- isAsleep -> isPulsing
- else -> true
- }
- }
-
- /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
- val isExpandToQsEnabled: Flow<Boolean> =
- combine(
- disableFlagsRepository.disableFlags,
- isShadeEnabled,
- keyguardRepository.isDozing,
- userSetupRepository.isUserSetupFlow,
- deviceProvisioningRepository.isDeviceProvisioned,
- ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
- isDeviceProvisioned &&
- // Disallow QS during setup if it's a simple user switcher. (The user intends to
- // use the lock screen user switcher, QS is not needed.)
- (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) &&
- isShadeEnabled &&
- disableFlags.isQuickSettingsEnabled() &&
- !isDozing
- }
-
- fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
- sceneInteractor.transitionState
- .flatMapLatest { state ->
- when (state) {
- is ObservableTransitionState.Idle ->
- if (state.scene == sceneKey) {
- flowOf(1f)
- } else {
- flowOf(0f)
- }
- is ObservableTransitionState.Transition ->
- if (state.toScene == sceneKey) {
- state.progress
- } else if (state.fromScene == sceneKey) {
- state.progress.map { progress -> 1 - progress }
- } else {
- flowOf(0f)
- }
- }
- }
- .distinctUntilChanged()
-
- fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
- sceneInteractor.transitionState
- .map { state ->
- when (state) {
- is ObservableTransitionState.Idle -> false
- is ObservableTransitionState.Transition ->
- state.isInitiatedByUserInput &&
- (state.toScene == sceneKey || state.fromScene == sceneKey)
- }
- }
- .distinctUntilChanged()
-
- /**
- * Return a flow for whether a user is interacting with an expandable shade component using
- * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
- * [expansion.first] checks the current value of the flow.
- */
- private fun userInteractingFlow(
- tracking: Flow<Boolean>,
- expansion: StateFlow<Float>
- ): Flow<Boolean> {
- return flow {
- // initial value is false
- emit(false)
- while (currentCoroutineContext().isActive) {
- // wait for tracking to become true
- tracking.first { it }
- emit(true)
- // wait for tracking to become false
- tracking.first { !it }
- // wait for expansion to complete in either direction
- expansion.first { it <= 0f || it >= 1f }
- // interaction complete
- emit(false)
- }
- }
- }
+fun createAnyExpansionFlow(
+ scope: CoroutineScope,
+ shadeExpansion: Flow<Float>,
+ qsExpansion: Flow<Float>
+): StateFlow<Float> {
+ return combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
+ .stateIn(scope, SharingStarted.Eagerly, 0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
new file mode 100644
index 0000000..d41c5a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Empty implementation of ShadeInteractor for System UI variants with no shade. */
+@SysUISingleton
+class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
+ private val inactiveFlowBoolean = MutableStateFlow(false)
+ private val inactiveFlowFloat = MutableStateFlow(0f)
+ override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
+ override val shadeExpansion: Flow<Float> = inactiveFlowFloat
+ override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
+ override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
+ override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
+ override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
+ override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
+ override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
+ override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
+ override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean
+ override val isUserInteracting: Flow<Boolean> = inactiveFlowBoolean
+ override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean
+ override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
new file mode 100644
index 0000000..68600e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
+import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
+import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
+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.map
+import kotlinx.coroutines.flow.stateIn
+
+/** The non-empty SceneInteractor implementation. */
+@SysUISingleton
+class ShadeInteractorImpl
+@Inject
+constructor(
+ @Application val scope: CoroutineScope,
+ deviceProvisioningRepository: DeviceProvisioningRepository,
+ disableFlagsRepository: DisableFlagsRepository,
+ dozeParams: DozeParameters,
+ keyguardRepository: KeyguardRepository,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ powerInteractor: PowerInteractor,
+ userSetupRepository: UserSetupRepository,
+ userSwitcherInteractor: UserSwitcherInteractor,
+ private val baseShadeInteractor: BaseShadeInteractor,
+) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
+ override val isShadeEnabled: StateFlow<Boolean> =
+ disableFlagsRepository.disableFlags
+ .map { it.isShadeEnabled() }
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
+
+ override val isAnyFullyExpanded: Flow<Boolean> =
+ anyExpansion.map { it >= 1f }.distinctUntilChanged()
+
+ override val isShadeFullyExpanded: Flow<Boolean> =
+ baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
+
+ override val isUserInteracting: Flow<Boolean> =
+ combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
+ .distinctUntilChanged()
+
+ override val isShadeTouchable: Flow<Boolean> =
+ combine(
+ powerInteractor.isAsleep,
+ keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
+ keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
+ deviceProvisioningRepository.isFactoryResetProtectionActive,
+ ) { isAsleep, goingToSleep, isPulsing, isFrpActive ->
+ when {
+ // Touches are disabled when Factory Reset Protection is active
+ isFrpActive -> false
+ // If the device is going to sleep, only accept touches if we're still
+ // animating
+ goingToSleep -> dozeParams.shouldControlScreenOff()
+ // If the device is asleep, only accept touches if there's a pulse
+ isAsleep -> isPulsing
+ else -> true
+ }
+ }
+
+ override val isExpandToQsEnabled: Flow<Boolean> =
+ combine(
+ disableFlagsRepository.disableFlags,
+ isShadeEnabled,
+ keyguardRepository.isDozing,
+ userSetupRepository.isUserSetupFlow,
+ deviceProvisioningRepository.isDeviceProvisioned,
+ ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned ->
+ isDeviceProvisioned &&
+ // Disallow QS during setup if it's a simple user switcher. (The user intends to
+ // use the lock screen user switcher, QS is not needed.)
+ (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) &&
+ isShadeEnabled &&
+ disableFlags.isQuickSettingsEnabled() &&
+ !isDozing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
new file mode 100644
index 0000000..2ac3193
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.currentCoroutineContext
+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.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.isActive
+
+/** ShadeInteractor implementation for the legacy codebase, e.g. NPVC. */
+@SysUISingleton
+class ShadeInteractorLegacyImpl
+@Inject
+constructor(
+ @Application val scope: CoroutineScope,
+ keyguardRepository: KeyguardRepository,
+ sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+ repository: ShadeRepository,
+) : BaseShadeInteractor {
+ /** The amount [0-1] that the shade has been opened */
+ override val shadeExpansion: Flow<Float> =
+ combine(
+ repository.lockscreenShadeExpansion,
+ keyguardRepository.statusBarState,
+ repository.legacyShadeExpansion,
+ repository.qsExpansion,
+ sharedNotificationContainerInteractor.isSplitShadeEnabled
+ ) {
+ lockscreenShadeExpansion,
+ statusBarState,
+ legacyShadeExpansion,
+ qsExpansion,
+ splitShadeEnabled ->
+ when (statusBarState) {
+ // legacyShadeExpansion is 1 instead of 0 when QS is expanded
+ StatusBarState.SHADE ->
+ if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
+ StatusBarState.KEYGUARD -> lockscreenShadeExpansion
+ // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
+ // the pointer is lifted and the lockscreen shade is fully expanded
+ StatusBarState.SHADE_LOCKED -> 1f
+ }
+ }
+ .distinctUntilChanged()
+
+ override val qsExpansion: StateFlow<Float> = repository.qsExpansion
+
+ override val isQsExpanded: StateFlow<Boolean> = repository.legacyIsQsExpanded
+
+ override val isQsBypassingShade: Flow<Boolean> = repository.legacyExpandImmediate
+ override val isQsFullscreen: Flow<Boolean> = repository.legacyQsFullscreen
+
+ override val anyExpansion: StateFlow<Float> =
+ createAnyExpansionFlow(scope, shadeExpansion, qsExpansion)
+
+ override val isAnyExpanded =
+ repository.legacyExpandedOrAwaitingInputTransfer.stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ false
+ )
+
+ override val isUserInteractingWithShade: Flow<Boolean> =
+ combine(
+ userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion),
+ repository.legacyLockscreenShadeTracking
+ ) { legacyShadeTracking, legacyLockscreenShadeTracking ->
+ legacyShadeTracking || legacyLockscreenShadeTracking
+ }
+
+ override val isUserInteractingWithQs: Flow<Boolean> =
+ userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
+
+ /**
+ * Return a flow for whether a user is interacting with an expandable shade component using
+ * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
+ * [expansion.first] checks the current value of the flow.
+ */
+ private fun userInteractingFlow(
+ tracking: Flow<Boolean>,
+ expansion: StateFlow<Float>
+ ): Flow<Boolean> {
+ return flow {
+ // initial value is false
+ emit(false)
+ while (currentCoroutineContext().isActive) {
+ // wait for tracking to become true
+ tracking.first { it }
+ emit(true)
+ // wait for tracking to become false
+ tracking.first { !it }
+ // wait for expansion to complete in either direction
+ expansion.first { it <= 0f || it >= 1f }
+ // interaction complete
+ emit(false)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
new file mode 100644
index 0000000..7cff8ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.shade.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** ShadeInteractor implementation for Scene Container. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeInteractorSceneContainerImpl
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ sceneInteractor: SceneInteractor,
+ sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
+) : BaseShadeInteractor {
+ override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+
+ private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+
+ override val qsExpansion: StateFlow<Float> =
+ combine(
+ sharedNotificationContainerInteractor.isSplitShadeEnabled,
+ shadeExpansion,
+ sceneBasedQsExpansion,
+ ) { isSplitShadeEnabled, shadeExpansion, qsExpansion ->
+ if (isSplitShadeEnabled) {
+ shadeExpansion
+ } else {
+ qsExpansion
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, 0f)
+
+ override val isQsExpanded: StateFlow<Boolean> =
+ qsExpansion
+ .map { it > 0 }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isQsBypassingShade: Flow<Boolean> =
+ sceneInteractor.transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> flowOf(false)
+ is ObservableTransitionState.Transition ->
+ flowOf(
+ state.toScene == SceneKey.QuickSettings &&
+ state.fromScene != SceneKey.Shade
+ )
+ }
+ }
+ .distinctUntilChanged()
+
+ override val isQsFullscreen: Flow<Boolean> =
+ sceneInteractor.transitionState
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> state.scene == SceneKey.QuickSettings
+ is ObservableTransitionState.Transition -> false
+ }
+ }
+ .distinctUntilChanged()
+
+ override val anyExpansion: StateFlow<Float> =
+ createAnyExpansionFlow(scope, shadeExpansion, qsExpansion)
+
+ override val isAnyExpanded =
+ anyExpansion
+ .map { it > 0f }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ override val isUserInteractingWithShade: Flow<Boolean> =
+ sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
+
+ override val isUserInteractingWithQs: Flow<Boolean> =
+ sceneBasedInteracting(sceneInteractor, SceneKey.QuickSettings)
+
+ /**
+ * Returns a flow that uses scene transition progress to and from a scene that is pulled down
+ * from the top of the screen to a 0-1 expansion amount float.
+ */
+ internal fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+ sceneInteractor.transitionState
+ .flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ if (state.scene == sceneKey) {
+ flowOf(1f)
+ } else {
+ flowOf(0f)
+ }
+ is ObservableTransitionState.Transition ->
+ if (state.toScene == sceneKey) {
+ state.progress
+ } else if (state.fromScene == sceneKey) {
+ state.progress.map { progress -> 1 - progress }
+ } else {
+ flowOf(0f)
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Returns a flow that uses scene transition data to determine whether the user is interacting
+ * with a scene that is pulled down from the top of the screen.
+ */
+ internal fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+ sceneInteractor.transitionState
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle -> false
+ is ObservableTransitionState.Transition ->
+ state.isInitiatedByUserInput &&
+ (state.toScene == sceneKey || state.fromScene == sceneKey)
+ }
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 20b9ede..af88081 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,13 +19,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneKey
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
/** Models UI state and handles user input for the shade scene. */
@SysUISingleton
@@ -34,6 +35,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c2863fb..d88fab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -75,14 +75,14 @@
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.policy.CallbackController;
+import dagger.Lazy;
+
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
-import dagger.Lazy;
-
/**
* This class takes the functions from IStatusBar that come in on
* binder pool threads and posts messages to get them onto the main
@@ -424,7 +424,7 @@
default void onTracingStateChanged(boolean enabled) { }
/**
- * Requests {@link com.android.systemui.accessibility.WindowMagnification} to invoke
+ * Requests {@link com.android.systemui.accessibility.Magnification} to invoke
* {@code android.view.accessibility.AccessibilityManager#
* setWindowMagnificationConnection(IWindowMagnificationConnection)}
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 2e3f3f8..ae765e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -21,7 +21,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -78,7 +80,8 @@
private val shadeRepository: ShadeRepository,
private val shadeInteractor: ShadeInteractor,
private val powerInteractor: PowerInteractor,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver,
) : Dumpable {
private var pulseHeight: Float = 0f
@@ -157,7 +160,8 @@
var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null
/** The touch helper responsible for the drag down animation. */
- val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context)
+ val touchHelper = DragDownHelper(falsingManager, falsingCollector, this,
+ naturalScrollingSettingObserver, context)
private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy {
splitShadeOverScrollerFactory.create({ qS }, { nsslController })
@@ -751,6 +755,7 @@
private val falsingManager: FalsingManager,
private val falsingCollector: FalsingCollector,
private val dragDownCallback: LockscreenShadeTransitionController,
+ private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver,
context: Context
) : Gefingerpoken {
@@ -765,6 +770,7 @@
private var draggedFarEnough = false
private var startingChild: ExpandableView? = null
private var lastHeight = 0f
+ private var isTrackpadReverseScroll = false
var isDraggingDown = false
private set
@@ -802,9 +808,11 @@
startingChild = null
initialTouchY = y
initialTouchX = x
+ isTrackpadReverseScroll = !naturalScrollingSettingObserver.isNaturalScrollingEnabled
+ && isTrackpadScroll(true, event)
}
MotionEvent.ACTION_MOVE -> {
- val h = y - initialTouchY
+ val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
// Adjust the touch slop if another gesture may be being performed.
val touchSlop =
if (event.classification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
@@ -834,7 +842,7 @@
val y = event.y
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> {
- lastHeight = y - initialTouchY
+ lastHeight = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
captureStartingChild(initialTouchX, initialTouchY)
dragDownCallback.dragDownAmount = lastHeight + dragDownAmountOnStart
if (startingChild != null) {
@@ -859,12 +867,14 @@
!isFalseTouch &&
dragDownCallback.canDragDown()
) {
- dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt())
+ val dragDown = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY)
+ dragDownCallback.onDraggedDown(startingChild, dragDown.toInt())
if (startingChild != null) {
expandCallback.setUserLockedChild(startingChild, false)
startingChild = null
}
isDraggingDown = false
+ isTrackpadReverseScroll = false
} else {
stopDragging()
return false
@@ -943,6 +953,7 @@
startingChild = null
}
isDraggingDown = false
+ isTrackpadReverseScroll = false
dragDownCallback.onDragDownReset()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 0b6e400..4d37335 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -92,6 +92,7 @@
private int mIndexOfFirstViewInShelf = -1;
private float mCornerAnimationDistance;
private float mActualWidth = -1;
+ private int mMaxIconsOnLockscreen;
private final RefactorFlag mSensitiveRevealAnim =
RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM);
private boolean mCanModifyColorOfNotifications;
@@ -136,6 +137,7 @@
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
+ mMaxIconsOnLockscreen = res.getInteger(R.integer.max_notif_icons_on_lockscreen);
ViewGroup.LayoutParams layoutParams = getLayoutParams();
final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
@@ -227,7 +229,9 @@
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
}
- viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
+ }
viewState.hideSensitive = false;
viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -271,6 +275,7 @@
}
private int getSpeedBumpIndex() {
+ NotificationIconContainerRefactor.assertInLegacyMode();
return mHostLayout.getSpeedBumpIndex();
}
@@ -280,6 +285,7 @@
*/
@VisibleForTesting
public void updateActualWidth(float fractionToShade, float shortestWidth) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
final float actualWidth = mAmbientState.isOnKeyguard()
? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade)
: getWidth();
@@ -290,6 +296,15 @@
mActualWidth = actualWidth;
}
+ private void setActualWidth(float actualWidth) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+ setBackgroundWidth((int) actualWidth);
+ if (mShelfIcons != null) {
+ mShelfIcons.setActualLayoutWidth((int) actualWidth);
+ }
+ mActualWidth = actualWidth;
+ }
+
@Override
public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
super.getBoundsOnScreen(outRect, clipToParent);
@@ -467,12 +482,26 @@
final float fractionToShade = Interpolators.STANDARD.getInterpolation(
mAmbientState.getFractionToShade());
- final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf);
- updateActualWidth(fractionToShade, shortestWidth);
+
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mAmbientState.isOnKeyguard()) {
+ float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1);
+ float shortestWidth = mShelfIcons.calculateWidthFor(numViews);
+ float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade);
+ setActualWidth(actualWidth);
+ } else {
+ setActualWidth(getWidth());
+ }
+ } else {
+ final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf);
+ updateActualWidth(fractionToShade, shortestWidth);
+ }
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
+ }
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
for (int i = 0; i < getHostLayoutChildCount(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index f8bc0ee..a85c440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -25,9 +25,7 @@
import com.android.internal.policy.SystemBarUtils
import com.android.internal.statusbar.StatusBarIcon
import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -39,21 +37,15 @@
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData
-import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.util.asIndenting
import com.android.systemui.util.kotlin.mapValuesNotNullTo
import com.android.systemui.util.kotlin.stateFlow
-import com.android.systemui.util.printCollection
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
-import dagger.Binds
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
-import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
@@ -134,6 +126,7 @@
): DisposableHandle {
return view.repeatWhenAttached {
lifecycleScope.launch {
+ view.setUseIncreasedIconScale(true)
launch {
viewModel.icons.bindIcons(
view,
@@ -229,7 +222,6 @@
->
FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
}
-
val failedBindings = mutableSetOf<String>()
val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
var prevIcons = NotificationIconsViewData()
@@ -237,64 +229,111 @@
val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons)
prevIcons = iconsData
+ // Lookup 1:1 group icon replacements
val replacingIcons: ArrayMap<String, StatusBarIcon> =
- iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) ->
- boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon
+ iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, notifKey) ->
+ boundViewsByNotifKey[notifKey]?.first?.statusBarIcon
}
- view.setReplacingIcons(replacingIcons)
-
- for (notifKey in iconsDiff.removed) {
- failedBindings.remove(notifKey)
- val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue
- view.removeView(child)
- job.cancel()
- }
-
- val toAdd: Sequence<String> =
- iconsDiff.added.asSequence().map { it.notifKey } + failedBindings
- for ((idx, notifKey) in toAdd.withIndex()) {
- val sbiv = viewStore.iconView(notifKey)
- if (sbiv == null) {
- failedBindings.add(notifKey)
- continue
+ view.withIconReplacements(replacingIcons) {
+ // Remove and unbind.
+ for (notifKey in iconsDiff.removed) {
+ failedBindings.remove(notifKey)
+ val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue
+ view.removeView(child)
+ job.cancel()
}
- // The view might still be transiently added if it was just removed and added again
- view.removeTransientView(sbiv)
- view.addView(sbiv, idx)
- boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
- boundViewsByNotifKey[notifKey] =
- Pair(
- sbiv,
- launch {
- launch { layoutParams.collect { sbiv.layoutParams = it } }
- bindIcon(notifKey, sbiv)
- },
- )
- }
- notifyBindingFailures(failedBindings)
-
- view.setChangingViewPositions(true)
-
- // Re-sort notification icons
- val expectedChildren =
- iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first }
- val childCount = view.childCount
- for (i in 0 until childCount) {
- val actual = view.getChildAt(i)
- val expected = expectedChildren[i]
- if (actual === expected) {
- continue
+ // Add and bind.
+ val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings
+ for ((idx, notifKey) in toAdd.withIndex()) {
+ // Lookup the StatusBarIconView from the store.
+ val sbiv = viewStore.iconView(notifKey)
+ if (sbiv == null) {
+ failedBindings.add(notifKey)
+ continue
+ }
+ // The view might still be transiently added if it was just removed and added
+ // again
+ view.removeTransientView(sbiv)
+ view.addView(sbiv, idx)
+ boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
+ boundViewsByNotifKey[notifKey] =
+ Pair(
+ sbiv,
+ launch {
+ launch { layoutParams.collect { sbiv.layoutParams = it } }
+ bindIcon(notifKey, sbiv)
+ },
+ )
}
- view.removeView(expected)
- view.addView(expected, i)
- }
- view.setChangingViewPositions(false)
- view.setReplacingIcons(null)
+ // Set the maximum number of icons to show in the container. Any icons over this
+ // amount will render as an "overflow dot".
+ val maxIconsAmount: Int =
+ when (iconsData.limitType) {
+ LimitType.MaximumIndex -> {
+ iconsData.visibleIcons
+ .asSequence()
+ .take(iconsData.iconLimit)
+ .count { info -> info.notifKey in boundViewsByNotifKey }
+ }
+ LimitType.MaximumAmount -> {
+ iconsData.iconLimit
+ }
+ }
+ view.setMaxIconsAmount(maxIconsAmount)
+
+ // Track the binding failures so that they appear in dumpsys.
+ notifyBindingFailures(failedBindings)
+
+ // Re-sort notification icons
+ view.changeViewPositions {
+ val expectedChildren: List<StatusBarIconView> =
+ iconsData.visibleIcons.mapNotNull {
+ boundViewsByNotifKey[it.notifKey]?.first
+ }
+ val childCount = view.childCount
+ for (i in 0 until childCount) {
+ val actual = view.getChildAt(i)
+ val expected = expectedChildren[i]
+ if (actual === expected) {
+ continue
+ }
+ view.removeView(expected)
+ view.addView(expected, i)
+ }
+ }
+ }
+ // Recalculate all icon positions, to reflect our updates.
+ view.calculateIconXTranslations()
}
}
+ /**
+ * Track which groups are being replaced with a different icon instance, but with the same
+ * visual icon. This prevents a weird animation where it looks like an icon disappears and
+ * reappears unchanged.
+ */
+ // TODO(b/305739416): Ideally we wouldn't swap out the StatusBarIconView at all, and instead use
+ // a single SBIV instance for the group. Then this whole concept can go away.
+ private inline fun <R> NotificationIconContainer.withIconReplacements(
+ replacements: ArrayMap<String, StatusBarIcon>,
+ block: () -> R
+ ): R {
+ setReplacingIcons(replacements)
+ return block().also { setReplacingIcons(null) }
+ }
+
+ /**
+ * Any invocations of [NotificationIconContainer.addView] /
+ * [NotificationIconContainer.removeView] inside of [block] will not cause a new add / remove
+ * animation.
+ */
+ private inline fun <R> NotificationIconContainer.changeViewPositions(block: () -> R): R {
+ setChangingViewPositions(true)
+ return block().also { setChangingViewPositions(false) }
+ }
+
/** External storage for [StatusBarIconView] instances. */
fun interface IconViewStore {
fun iconView(key: String): StatusBarIconView?
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index b11eca2c..9cb60d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,10 +15,13 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.content.res.Resources
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
import javax.inject.Inject
@@ -35,9 +38,12 @@
iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ @Main resources: Resources,
shadeInteractor: ShadeInteractor,
) {
+ private val maxIcons = resources.getInteger(R.integer.max_notif_icons_on_aod)
+
/** Are changes to the icon container animated? */
val areContainerChangesAnimated: Flow<Boolean> =
combine(
@@ -67,7 +73,8 @@
val icons: Flow<NotificationIconsViewData> =
iconsInteractor.aodNotifs.map { entries ->
NotificationIconsViewData(
- visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+ visibleIcons = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+ iconLimit = maxIcons,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index 1560106..8484fdc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -29,8 +30,23 @@
/** [NotificationIconsViewData] indicating which icons to display in the view. */
val icons: Flow<NotificationIconsViewData> =
interactor.filteredNotifSet().map { entries ->
+ var firstAmbient = 0
+ val visibleKeys = buildList {
+ for (entry in entries) {
+ entry.toIconInfo(entry.shelfIcon)?.let { info ->
+ add(info)
+ // NOTE: we assume that all ambient notifications will be at the end of the
+ // list
+ if (!entry.isAmbient) {
+ firstAmbient++
+ }
+ }
+ }
+ }
NotificationIconsViewData(
- visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) },
+ visibleKeys,
+ iconLimit = firstAmbient,
+ limitType = LimitType.MaximumIndex,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index c03a4a5..af37e49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -15,9 +15,12 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.content.res.Resources
import android.graphics.Rect
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
@@ -44,9 +47,12 @@
headsUpIconInteractor: HeadsUpNotificationIconInteractor,
keyguardInteractor: KeyguardInteractor,
notificationsInteractor: ActiveNotificationsInteractor,
+ @Main resources: Resources,
shadeInteractor: ShadeInteractor,
) {
+ private val maxIcons = resources.getInteger(R.integer.max_notif_static_icons)
+
/** Are changes to the icon container animated? */
val animationsEnabled: Flow<Boolean> =
combine(
@@ -77,7 +83,8 @@
val icons: Flow<NotificationIconsViewData> =
iconsInteractor.statusBarNotifs.map { entries ->
NotificationIconsViewData(
- visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+ visibleIcons = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+ iconLimit = maxIcons,
)
}
@@ -86,7 +93,7 @@
headsUpIconInteractor.isolatedNotification
.combine(icons) { isolatedNotif, iconsViewData ->
isolatedNotif?.let {
- iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+ iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
}
}
.pairwise(initialValue = null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
index 867be84..e8756d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt
@@ -23,16 +23,33 @@
/** Encapsulates the collection of notification icons present on the device. */
data class NotificationIconsViewData(
/** Icons that are visible in the container. */
- val visibleKeys: List<NotificationIconInfo> = emptyList(),
- /** Keys of icons that are "behind" the overflow dot. */
- val collapsedKeys: Set<String> = emptySet(),
- /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */
- val forceShowDot: Boolean = false,
+ val visibleIcons: List<NotificationIconInfo> = emptyList(),
+ /** Limit applied to the [visibleIcons]; can be interpreted different based on [limitType]. */
+ val iconLimit: Int = visibleIcons.size,
+ /** How [iconLimit] is applied to [visibleIcons]. */
+ val limitType: LimitType = LimitType.MaximumAmount,
) {
+ // TODO(b/305739416): This can be removed once we are no longer looking up the StatusBarIconView
+ // instances from outside of the view-binder layer. Ideally, we would just use MaximumAmount,
+ // and apply it directly to the list of visibleIcons by truncating the list to that amount.
+ // At the time of this writing, we cannot do that because looking up the SBIV can fail, and so
+ // we need additional icons to fall-back to.
+ /** Determines how a limit to the icons is to be applied. */
+ enum class LimitType {
+ /** The [Int] limit is a maximum amount of icons to be displayed. */
+ MaximumAmount,
+ /**
+ * The [Int] limit is a maximum index into the
+ * [list of visible icons][NotificationIconsViewData.visibleIcons] to be displayed; any
+ * icons beyond that index should be omitted.
+ */
+ MaximumIndex,
+ }
+
/** The difference between two [NotificationIconsViewData]s. */
data class Diff(
/** Icons added in the newer dataset. */
- val added: List<NotificationIconInfo> = emptyList(),
+ val added: List<String> = emptyList(),
/** Icons removed from the older dataset. */
val removed: List<String> = emptyList(),
/**
@@ -44,7 +61,7 @@
* same group. A view binder can use this information for special animations for this
* specific change.
*/
- val groupReplacements: Map<String, NotificationIconInfo> = emptyMap(),
+ val groupReplacements: Map<String, String> = emptyMap(),
)
companion object {
@@ -56,23 +73,20 @@
new: NotificationIconsViewData,
prev: NotificationIconsViewData
): Diff {
- val added: List<NotificationIconInfo> =
- new.visibleKeys.filter {
- it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
- }
+ val prevKeys = prev.visibleIcons.asSequence().map { it.notifKey }.toSet()
+ val newKeys = new.visibleIcons.asSequence().map { it.notifKey }.toSet()
+ val added: List<String> = newKeys.mapNotNull { key -> key.takeIf { it !in prevKeys } }
val removed: List<NotificationIconInfo> =
- prev.visibleKeys.filter {
- it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey }
- }
+ prev.visibleIcons.filter { it.notifKey !in newKeys }
val groupsToShow: Set<IconGroupInfo> =
- new.visibleKeys.asSequence().map { it.groupInfo }.toSet()
- val replacements: ArrayMap<String, NotificationIconInfo> =
+ new.visibleIcons.asSequence().map { it.groupInfo }.toSet()
+ val replacements: ArrayMap<String, String> =
removed
.asSequence()
.filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
.groupBy { it.groupInfo.groupKey }
.mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
- vs.takeIf { it.size == 1 }?.get(0)
+ vs.takeIf { it.size == 1 }?.get(0)?.notifKey
}
return Diff(added, removed.map { it.notifKey }, replacements)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 9ff416a..9f2b0a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
@@ -141,7 +142,11 @@
}
class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
- VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") {
+ VisualInterruptionFilter(
+ types = setOf(PEEK),
+ reason = "has old `when`",
+ uiEventId = HUN_SUPPRESSED_OLD_WHEN
+ ) {
private fun whenAge(entry: NotificationEntry) =
systemClock.currentTimeMillis() - entry.sbn.notification.`when`
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index b44a367..2707ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -18,6 +18,7 @@
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.os.PowerManager
+import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -37,6 +38,10 @@
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_ONLY_BY_DND
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA
import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -52,6 +57,8 @@
val logReason: String
val shouldLog: Boolean
val isWarning: Boolean
+ val uiEventId: UiEventEnum?
+ val eventLogData: EventLogData?
}
private enum class DecisionImpl(
@@ -60,7 +67,9 @@
override val wouldFsiWithoutDnd: Boolean = shouldFsi,
val supersedesDnd: Boolean = false,
override val shouldLog: Boolean = true,
- override val isWarning: Boolean = false
+ override val isWarning: Boolean = false,
+ override val uiEventId: UiEventEnum? = null,
+ override val eventLogData: EventLogData? = null
) : Decision {
NO_FSI_NO_FULL_SCREEN_INTENT(
false,
@@ -73,9 +82,17 @@
NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(
false,
"suppressive group alert behavior",
- isWarning = true
+ isWarning = true,
+ uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+ eventLogData = EventLogData("231322873", "groupAlertBehavior")
),
- NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata", isWarning = true),
+ NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(
+ false,
+ "suppressive bubble metadata",
+ isWarning = true,
+ uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
+ eventLogData = EventLogData("274759612", "bubbleMetadata")
+ ),
NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"),
FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"),
FSI_DEVICE_DREAMING(true, "device is dreaming"),
@@ -84,7 +101,13 @@
FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"),
FSI_LOCKED_SHADE(true, "locked shade"),
FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"),
- NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard", isWarning = true),
+ NO_FSI_NO_HUN_OR_KEYGUARD(
+ false,
+ "no HUN or keyguard",
+ isWarning = true,
+ uiEventId = FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD,
+ eventLogData = EventLogData("231322873", "no hun or keyguard")
+ ),
NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false),
NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index f2ade34..4045380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -49,6 +49,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.EventLog;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
@@ -81,6 +82,7 @@
private final DeviceProvisionedController mDeviceProvisionedController;
private final SystemClock mSystemClock;
private final GlobalSettings mGlobalSettings;
+ private final EventLog mEventLog;
@VisibleForTesting
protected boolean mUseHeadsUp = false;
@@ -129,7 +131,8 @@
UserTracker userTracker,
DeviceProvisionedController deviceProvisionedController,
SystemClock systemClock,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ EventLog eventLog) {
mPowerManager = powerManager;
mBatteryController = batteryController;
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -144,6 +147,7 @@
mDeviceProvisionedController = deviceProvisionedController;
mSystemClock = systemClock;
mGlobalSettings = globalSettings;
+ mEventLog = eventLog;
ContentObserver headsUpObserver = new ContentObserver(mainHandler) {
@Override
public void onChange(boolean selfChange) {
@@ -369,7 +373,7 @@
// explicitly prevent logging for this (frequent) case
return;
case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR:
- android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
+ mEventLog.writeEvent(0x534e4554, "231322873", uid,
"groupAlertBehavior");
mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid,
packageName);
@@ -377,7 +381,7 @@
decision + ": GroupAlertBehavior will prevent HUN");
return;
case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA:
- android.util.EventLog.writeEvent(0x534e4554, "274759612", uid,
+ mEventLog.writeEvent(0x534e4554, "274759612", uid,
"bubbleMetadata");
mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid,
packageName);
@@ -385,7 +389,7 @@
decision + ": BubbleMetadata may prevent HUN");
return;
case NO_FSI_NO_HUN_OR_KEYGUARD:
- android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
+ mEventLog.writeEvent(0x534e4554, "231322873", uid,
"no hun or keyguard");
mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName);
mLogger.logNoFullscreenWarning(entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
index d7f0baf..f732e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
@@ -17,6 +17,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.RefactorFlagUtils
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
@@ -62,6 +63,21 @@
wrapped.removeSuppressor(suppressor)
}
+ override fun addCondition(condition: VisualInterruptionCondition) = notValidInLegacyMode()
+
+ override fun removeCondition(condition: VisualInterruptionCondition) = notValidInLegacyMode()
+
+ override fun addFilter(filter: VisualInterruptionFilter) = notValidInLegacyMode()
+
+ override fun removeFilter(filter: VisualInterruptionFilter) = notValidInLegacyMode()
+
+ private fun notValidInLegacyMode() {
+ RefactorFlagUtils.assertOnEngBuild(
+ "This method is only implemented in VisualInterruptionDecisionProviderImpl, " +
+ "and so should only be called when FLAG_VISUAL_INTERRUPTIONS_REFACTOR is enabled."
+ )
+ }
+
override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index da8474e..de8863c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.interruption
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.statusbar.notification.collection.NotificationEntry
/**
@@ -51,33 +52,77 @@
val wouldInterruptWithoutDnd: Boolean
}
- /**
- * Initializes the provider.
- *
- * Must be called before any method except [addLegacySuppressor].
- */
+ /** Initializes the provider. */
fun start() {}
/**
- * Adds a [component][suppressor] that can suppress visual interruptions.
+ * Adds a [NotificationInterruptSuppressor] that can suppress visual interruptions.
*
- * This class may call suppressors in any order.
+ * This method may be called before [start] has been called.
+ *
+ * This class may call suppressors, conditions, and filters in any order.
*
* @param[suppressor] the suppressor to add
*/
fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor)
/**
- * Removes a [component][suppressor] that can suppress visual interruptions.
+ * Removes a previously-added suppressor.
+ *
+ * This method may be called before [start] has been called.
*
* @param[suppressor] the suppressor to remove
*/
- fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor)
+ @VisibleForTesting fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor)
+
+ /**
+ * Adds a [VisualInterruptionCondition] that can suppress visual interruptions without examining
+ * individual notifications.
+ *
+ * This method may be called before [start] has been called.
+ *
+ * This class may call suppressors, conditions, and filters in any order.
+ *
+ * @param[condition] the condition to add
+ */
+ fun addCondition(condition: VisualInterruptionCondition)
+
+ /**
+ * Removes a previously-added condition.
+ *
+ * This method may be called before [start] has been called.
+ *
+ * @param[condition] the condition to remove
+ */
+ @VisibleForTesting fun removeCondition(condition: VisualInterruptionCondition)
+
+ /**
+ * Adds a [VisualInterruptionFilter] that can suppress visual interruptions based on individual
+ * notifications.
+ *
+ * This method may be called before [start] has been called.
+ *
+ * This class may call suppressors, conditions, and filters in any order.
+ *
+ * @param[filter] the filter to add
+ */
+ fun addFilter(filter: VisualInterruptionFilter)
+
+ /**
+ * Removes a previously-added filter.
+ *
+ * This method may be called before [start] has been called.
+ *
+ * @param[filter] the filter to remove
+ */
+ @VisibleForTesting fun removeFilter(filter: VisualInterruptionFilter)
/**
* Decides whether a [notification][entry] should display as heads-up or not, but does not log
* that decision.
*
+ * [start] must be called before this method can be called.
+ *
* @param[entry] the notification that this decision is about
* @return the decision to display that notification as heads-up or not
*/
@@ -93,6 +138,8 @@
* If the device is dozing, the decision will consider whether the notification should "pulse"
* (wake the screen up and display the ambient view of the notification).
*
+ * [start] must be called before this method can be called.
+ *
* @see[makeUnloggedHeadsUpDecision]
*
* @param[entry] the notification that this decision is about
@@ -106,6 +153,8 @@
*
* The returned decision can be logged by passing it to [logFullScreenIntentDecision].
*
+ * [start] must be called before this method can be called.
+ *
* @see[makeAndLogHeadsUpDecision]
*
* @param[entry] the notification that this decision is about
@@ -116,6 +165,8 @@
/**
* Logs a previous [decision] to launch a full-screen intent or not.
*
+ * [start] must be called before this method can be called.
+ *
* @param[decision] the decision to log
*/
fun logFullScreenIntentDecision(decision: FullScreenIntentDecision)
@@ -123,6 +174,8 @@
/**
* Decides whether a [notification][entry] should display as a bubble or not.
*
+ * [start] must be called before this method can be called.
+ *
* @param[entry] the notification that this decision is about
* @return the decision to display that notification as a bubble or not
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index c0a1a32..2b6e1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -20,12 +20,15 @@
import android.os.PowerManager
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
@@ -33,6 +36,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.EventLog
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -43,6 +47,7 @@
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val batteryController: BatteryController,
deviceProvisionedController: DeviceProvisionedController,
+ private val eventLog: EventLog,
private val globalSettings: GlobalSettings,
private val headsUpManager: HeadsUpManager,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
@@ -52,14 +57,25 @@
private val powerManager: PowerManager,
private val statusBarStateController: StatusBarStateController,
private val systemClock: SystemClock,
+ private val uiEventLogger: UiEventLogger,
private val userTracker: UserTracker,
) : VisualInterruptionDecisionProvider {
+ interface Loggable {
+ val uiEventId: UiEventEnum?
+ val eventLogData: EventLogData?
+ }
+
private class DecisionImpl(
override val shouldInterrupt: Boolean,
override val logReason: String
) : Decision
- private data class LoggableDecision private constructor(val decision: DecisionImpl) {
+ private data class LoggableDecision
+ private constructor(
+ val decision: DecisionImpl,
+ override val uiEventId: UiEventEnum? = null,
+ override val eventLogData: EventLogData? = null
+ ) : Loggable {
companion object {
val unsuppressed =
LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed"))
@@ -74,7 +90,9 @@
fun suppressed(suppressor: VisualInterruptionSuppressor) =
LoggableDecision(
- DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason)
+ DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason),
+ uiEventId = suppressor.uiEventId,
+ eventLogData = suppressor.eventLogData
)
}
}
@@ -82,7 +100,7 @@
private class FullScreenIntentDecisionImpl(
val entry: NotificationEntry,
private val fsiDecision: FullScreenIntentDecisionProvider.Decision
- ) : FullScreenIntentDecision {
+ ) : FullScreenIntentDecision, Loggable {
var hasBeenLogged = false
override val shouldInterrupt
@@ -99,6 +117,12 @@
val isWarning
get() = fsiDecision.isWarning
+
+ override val uiEventId
+ get() = fsiDecision.uiEventId
+
+ override val eventLogData
+ get() = fsiDecision.eventLogData
}
private val fullScreenIntentDecisionProvider =
@@ -147,23 +171,23 @@
legacySuppressors.remove(suppressor)
}
- fun addCondition(condition: VisualInterruptionCondition) {
+ override fun addCondition(condition: VisualInterruptionCondition) {
conditions.add(condition)
condition.start()
}
@VisibleForTesting
- fun removeCondition(condition: VisualInterruptionCondition) {
+ override fun removeCondition(condition: VisualInterruptionCondition) {
conditions.remove(condition)
}
- fun addFilter(filter: VisualInterruptionFilter) {
+ override fun addFilter(filter: VisualInterruptionFilter) {
filters.add(filter)
filter.start()
}
@VisibleForTesting
- fun removeFilter(filter: VisualInterruptionFilter) {
+ override fun removeFilter(filter: VisualInterruptionFilter) {
filters.remove(filter)
}
@@ -214,9 +238,10 @@
private fun logDecision(
type: VisualInterruptionType,
entry: NotificationEntry,
- loggable: LoggableDecision
+ loggableDecision: LoggableDecision
) {
- logger.logDecision(type.name, entry, loggable.decision)
+ logger.logDecision(type.name, entry, loggableDecision.decision)
+ logEvents(entry, loggableDecision)
}
override fun makeUnloggedFullScreenIntentDecision(
@@ -250,6 +275,14 @@
}
logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning)
+ logEvents(decision.entry, decision)
+ }
+
+ private fun logEvents(entry: NotificationEntry, loggable: Loggable) {
+ loggable.uiEventId?.let { uiEventLogger.log(it, entry.sbn.uid, entry.sbn.packageName) }
+ loggable.eventLogData?.let {
+ eventLog.writeEvent(0x534e4554, it.number, entry.sbn.uid, it.description)
+ }
}
private fun checkSuppressInterruptions(entry: NotificationEntry) =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index 39199df..2047c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -18,6 +18,7 @@
import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
/**
* A reason why visual interruptions might be suppressed.
@@ -43,6 +44,9 @@
* @see VisualInterruptionFilter
*/
sealed interface VisualInterruptionSuppressor {
+ /** Data to be logged in the EventLog when an interruption is suppressed. */
+ data class EventLogData(val number: String, val description: String)
+
/** The type(s) of interruption that this suppresses. */
val types: Set<VisualInterruptionType>
@@ -52,6 +56,9 @@
/** An optional UiEvent ID to be recorded when this suppresses an interruption. */
val uiEventId: UiEventEnum?
+ /** Optional data to be logged in the EventLog when this suppresses an interruption. */
+ val eventLogData: EventLogData?
+
/**
* Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
* any other methods are called on the suppressor.
@@ -63,7 +70,8 @@
abstract class VisualInterruptionCondition(
override val types: Set<VisualInterruptionType>,
override val reason: String,
- override val uiEventId: UiEventEnum? = null
+ override val uiEventId: UiEventEnum? = null,
+ override val eventLogData: EventLogData? = null
) : VisualInterruptionSuppressor {
/** @return true if these interruptions should be suppressed right now. */
abstract fun shouldSuppress(): Boolean
@@ -73,7 +81,8 @@
abstract class VisualInterruptionFilter(
override val types: Set<VisualInterruptionType>,
override val reason: String,
- override val uiEventId: UiEventEnum? = null
+ override val uiEventId: UiEventEnum? = null,
+ override val eventLogData: EventLogData? = null
) : VisualInterruptionSuppressor {
/**
* @param entry the notification to consider suppressing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8f1e59d..7c8d762 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.util.DumpUtilsKt;
@@ -229,17 +230,14 @@
@Override
public void setBelowSpeedBump(boolean below) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
super.setBelowSpeedBump(below);
if (below != mIsBelowSpeedBump) {
mIsBelowSpeedBump = below;
updateBackgroundTint();
- onBelowSpeedBumpChanged();
}
}
- protected void onBelowSpeedBumpChanged() {
- }
-
/**
* Sets the tint color of the background
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 6edab4d..49674d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.util.Compile;
@@ -400,6 +401,7 @@
* @param below true if it is below.
*/
public void setBelowSpeedBump(boolean below) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
}
public int getPinnedHeadsUpHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index 33473a6..d0c5c82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -26,6 +26,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
/**
* A state of an expandable view
@@ -162,7 +163,9 @@
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
// apply below shelf speed bump
- expandableView.setBelowSpeedBump(this.belowSpeedBump);
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ expandableView.setBelowSpeedBump(this.belowSpeedBump);
+ }
// apply clipping
final float oldClipTopAmount = expandableView.getClipTopAmount();
@@ -217,7 +220,9 @@
expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
// apply below the speed bump
- expandableView.setBelowSpeedBump(this.belowSpeedBump);
+ if (!NotificationIconContainerRefactor.isEnabled()) {
+ expandableView.setBelowSpeedBump(this.belowSpeedBump);
+ }
// start hiding sensitive animation
expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 14ec08f35..46488c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4668,22 +4668,6 @@
return mClearAllInProgress;
}
- public boolean isFooterViewNotGone() {
- return mFooterView != null
- && mFooterView.getVisibility() != View.GONE
- && !mFooterView.willBeGone();
- }
-
- public boolean isFooterViewContentVisible() {
- return mFooterView != null && mFooterView.isContentVisible();
- }
-
- public int getFooterViewHeightWithPadding() {
- return mFooterView == null ? 0 : mFooterView.getHeight()
- + mPaddingBetweenElements
- + mGapHeight;
- }
-
/**
* @return the padding after the media header on the lockscreen
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2cf0c26..3e140a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -858,10 +858,6 @@
return row.getVisibility() == View.VISIBLE;
}
- public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
- return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView);
- }
-
public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
mView.addOnExpandedHeightChangedListener(listener);
}
@@ -1261,18 +1257,6 @@
mView.setPanelFlinging(flinging);
}
- public boolean isFooterViewNotGone() {
- return mView.isFooterViewNotGone();
- }
-
- public boolean isFooterViewContentVisible() {
- return mView.isFooterViewContentVisible();
- }
-
- public int getFooterViewHeightWithPadding() {
- return mView.getFooterViewHeightWithPadding();
- }
-
/**
* Sets whether the bouncer is currently showing. Should only be called from
* {@link CentralSurfaces}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index eb1c17a..c2c5eed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -73,6 +73,11 @@
}
.distinctUntilChanged()
+ val isSplitShadeEnabled: Flow<Boolean> =
+ configurationBasedDimensions
+ .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade }
+ .distinctUntilChanged()
+
/** Top position (without translation) of the shared container. */
fun setTopPosition(top: Float) {
_topPosition.value = top
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 22b9298..60a4606 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -42,25 +41,23 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.camera.CameraIntents;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -97,7 +94,6 @@
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final PowerManager mPowerManager;
- private final VibratorHelper mVibratorHelper;
private final Optional<Vibrator> mVibratorOptional;
private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
@@ -108,8 +104,6 @@
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
private final QSHost mQSHost;
- private final FeatureFlags mFeatureFlags;
-
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -139,15 +133,13 @@
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
PowerManager powerManager,
- VibratorHelper vibratorHelper,
Optional<Vibrator> vibratorOptional,
DisableFlagsLogger disableFlagsLogger,
@DisplayId int displayId,
Lazy<CameraLauncher> cameraLauncherLazy,
UserTracker userTracker,
QSHost qsHost,
- ActivityStarter activityStarter,
- FeatureFlags featureFlags) {
+ ActivityStarter activityStarter) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
mContext = context;
@@ -168,14 +160,12 @@
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mPowerManager = powerManager;
- mVibratorHelper = vibratorHelper;
mVibratorOptional = vibratorOptional;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
mQSHost = qsHost;
- mFeatureFlags = featureFlags;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
@@ -544,12 +534,8 @@
@VisibleForTesting
void vibrateOnNavigationKeyDown() {
- if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- mShadeViewController.performHapticFeedback(
- HapticFeedbackConstants.GESTURE_START
- );
- } else {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
+ mShadeViewController.performHapticFeedback(
+ HapticFeedbackConstants.GESTURE_START
+ );
}
}
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 2e5717d..cd7a9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -139,6 +139,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -159,6 +160,7 @@
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -583,6 +585,8 @@
private final InteractionJankMonitor mJankMonitor;
+ private final SceneContainerFlags mSceneContainerFlags;
+
/**
* Public constructor for CentralSurfaces.
*
@@ -696,7 +700,8 @@
AlternateBouncerInteractor alternateBouncerInteractor,
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
- ActivityStarter activityStarter
+ ActivityStarter activityStarter,
+ SceneContainerFlags sceneContainerFlags
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -793,6 +798,7 @@
mUserTracker = userTracker;
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
+ mSceneContainerFlags = sceneContainerFlags;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1247,7 +1253,7 @@
// Set up the quick settings tile panel
final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
- if (container != null) {
+ if (container != null && !mSceneContainerFlags.isEnabled()) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
ExtensionFragmentListener.attachExtensonToFragment(
@@ -1449,7 +1455,7 @@
return (v, event) -> {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mShadeController.onStatusBarTouch(event);
}
return getNotificationShadeWindowView().onTouchEvent(event);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 3b3d8b6..1f9952a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -40,7 +40,7 @@
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -115,7 +115,6 @@
private int mAodIconTint;
private boolean mAodIconsVisible;
private boolean mShowLowPriority = true;
- private boolean mIsStatusViewMigrated = false;
@VisibleForTesting
final NotificationListener.NotificationSettingsListener mSettingsListener =
@@ -159,7 +158,6 @@
mStatusBarWindowController = statusBarWindowController;
mScreenOffAnimationController = screenOffAnimationController;
notificationListener.addNotificationSettingsListener(mSettingsListener);
- mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW);
initializeNotificationAreaViews(context);
reloadAodColor();
darkIconDispatcher.addDarkReceiver(this);
@@ -551,7 +549,7 @@
return;
}
if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
- if (!mIsStatusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
}
mAodIcons.setAlpha(0);
@@ -563,14 +561,14 @@
.start();
} else {
mAodIcons.setAlpha(1.0f);
- if (!mIsStatusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mAodIcons.setTranslationY(0);
}
}
}
private void animateInAodIconTranslation() {
- if (!mIsStatusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mAodIcons.animate()
.setInterpolator(Interpolators.DECELERATE_QUINT)
.translationY(0)
@@ -673,7 +671,7 @@
}
} else {
mAodIcons.setAlpha(1.0f);
- if (!mIsStatusViewMigrated) {
+ if (!KeyguardShadeMigrationNssl.isEnabled()) {
mAodIcons.setTranslationY(0);
}
mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
index c207324..2235035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java
@@ -24,6 +24,8 @@
import android.util.MathUtils;
import android.util.TimeUtils;
+import androidx.annotation.VisibleForTesting;
+
import com.android.app.animation.Interpolators;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.Dumpable;
@@ -96,13 +98,14 @@
private final KeyguardStateController mKeyguardStateController;
private final StatusBarStateController mStatusBarStateController;
private final CommandQueue mCommandQueue;
- private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
+ private GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
private boolean mTransitionDeferring;
private long mTransitionDeferringStartTime;
private long mTransitionDeferringDuration;
private boolean mTransitionPending;
private boolean mTintChangePending;
+ private boolean mNavigationButtonsForcedVisible;
private float mPendingDarkIntensity;
private ValueAnimator mTintAnimator;
private float mDarkIntensity;
@@ -137,13 +140,16 @@
mContext = context;
mDisplayId = mContext.getDisplayId();
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
- mHandler, mContext, null);
+ mHandler, mContext, this::onNavigationSettingsChanged);
+ mGestureNavigationSettingsObserver.register();
+ onNavigationSettingsChanged();
}
/** Call to cleanup the LightBarTransitionsController when done with it. */
public void destroy() {
mCommandQueue.removeCallback(mCallback);
mStatusBarStateController.removeCallback(mCallback);
+ mGestureNavigationSettingsObserver.unregister();
}
public void saveState(Bundle outState) {
@@ -199,6 +205,12 @@
mTransitionPending = false;
}
+ @VisibleForTesting
+ void setNavigationSettingsObserver(GestureNavigationSettingsObserver observer) {
+ mGestureNavigationSettingsObserver = observer;
+ onNavigationSettingsChanged();
+ }
+
public void setIconsDark(boolean dark, boolean animate) {
if (!animate) {
setIconTintInternal(dark ? 1.0f : 0.0f);
@@ -253,6 +265,28 @@
mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount));
}
+ public void onDozeAmountChanged(float linear, float eased) {
+ mDozeAmount = eased;
+ dispatchDark();
+ }
+
+ /**
+ * Called when the navigation settings change.
+ */
+ private void onNavigationSettingsChanged() {
+ mNavigationButtonsForcedVisible =
+ mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ }
+
+ /**
+ * Return whether to use the tint calculated in this class for nav icons.
+ */
+ public boolean supportsIconTintForNavMode(int navigationMode) {
+ // In gesture mode, we already do region sampling to update tint based on content beneath.
+ return !QuickStepContract.isGesturalMode(navigationMode)
+ || mNavigationButtonsForcedVisible;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.print(" mTransitionDeferring="); pw.print(mTransitionDeferring);
@@ -271,20 +305,7 @@
pw.print(" mPendingDarkIntensity="); pw.print(mPendingDarkIntensity);
pw.print(" mDarkIntensity="); pw.print(mDarkIntensity);
pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity);
- }
-
- public void onDozeAmountChanged(float linear, float eased) {
- mDozeAmount = eased;
- dispatchDark();
- }
-
- /**
- * Return whether to use the tint calculated in this class for nav icons.
- */
- public boolean supportsIconTintForNavMode(int navigationMode) {
- // In gesture mode, we already do region sampling to update tint based on content beneath.
- return !QuickStepContract.isGesturalMode(navigationMode)
- || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ pw.print(" mAreNavigationButtonForcedVisible="); pw.println(mNavigationButtonsForcedVisible);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 9e5fd95..00e78a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -141,8 +141,10 @@
private int mMaxStaticIcons;
private boolean mDozing;
private boolean mOnLockScreen;
- private boolean mOverrideIconColor;
+ private int mSpeedBumpIndex = -1;
+ private int mMaxIcons = Integer.MAX_VALUE;
+ private boolean mOverrideIconColor;
private boolean mIsStaticLayout = true;
private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
@@ -153,7 +155,6 @@
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
- private int mSpeedBumpIndex = -1;
private int mIconSize;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
@@ -170,6 +171,7 @@
private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
private Runnable mIsolatedIconAnimationEndRunnable;
+ private boolean mUseIncreasedIconScale;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -436,18 +438,24 @@
if (numIcons == 0) {
return 0f;
}
- final float contentWidth =
- mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1);
- return getActualPaddingStart()
- + contentWidth
- + getActualPaddingEnd();
+ final float contentWidth;
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ contentWidth = mIconSize * numIcons;
+ } else {
+ contentWidth = mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1);
+ }
+ return getActualPaddingStart() + contentWidth + getActualPaddingEnd();
}
@VisibleForTesting
boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount,
int maxVisibleIcons) {
- return speedBumpIndex != -1 && i >= speedBumpIndex
- && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ return i >= maxVisibleIcons && iconAppearAmount > 0.0f;
+ } else {
+ return speedBumpIndex != -1 && i >= speedBumpIndex
+ && iconAppearAmount > 0.0f || i >= maxVisibleIcons;
+ }
}
@VisibleForTesting
@@ -502,9 +510,8 @@
firstOverflowIndex = i;
mVisualOverflowStart = translationX;
}
- final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
- ? ((StatusBarIconView) view).getIconScaleIncreased()
- : 1f;
+
+ final float drawingScale = getDrawingScale(view);
translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
}
mIsShowingOverflowDot = false;
@@ -554,9 +561,26 @@
}
}
+ private float getDrawingScale(View view) {
+ final boolean useIncreasedScale = NotificationIconContainerRefactor.isEnabled()
+ ? mUseIncreasedIconScale
+ : mOnLockScreen;
+ return useIncreasedScale && view instanceof StatusBarIconView
+ ? ((StatusBarIconView) view).getIconScaleIncreased()
+ : 1f;
+ }
+
+ public void setUseIncreasedIconScale(boolean useIncreasedIconScale) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+ mUseIncreasedIconScale = useIncreasedIconScale;
+ }
+
private int getMaxVisibleIcons(int childCount) {
- return mOnLockScreen ? mMaxIconsOnAod :
- mIsStaticLayout ? mMaxStaticIcons : childCount;
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ return mMaxIcons;
+ } else {
+ return mOnLockScreen ? mMaxIconsOnAod : mIsStaticLayout ? mMaxStaticIcons : childCount;
+ }
}
private float getLayoutEnd() {
@@ -673,9 +697,15 @@
}
public void setSpeedBumpIndex(int speedBumpIndex) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
mSpeedBumpIndex = speedBumpIndex;
}
+ public void setMaxIconsAmount(int maxIcons) {
+ if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return;
+ mMaxIcons = maxIcons;
+ }
+
public int getIconSize() {
return mIconSize;
}
@@ -740,6 +770,7 @@
* configured to. Depending on these values, the layout of the AOD icons change.
*/
public void setOnLockScreen(boolean onLockScreen) {
+ NotificationIconContainerRefactor.assertInLegacyMode();
mOnLockScreen = onLockScreen;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index fb586ea..1a17e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealScrim
@@ -34,8 +35,6 @@
import com.android.app.tracing.TraceUtils
import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
/**
* When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely
@@ -68,7 +67,6 @@
private val interactionJankMonitor: InteractionJankMonitor,
private val powerManager: PowerManager,
private val handler: Handler = Handler(),
- private val featureFlags: FeatureFlags,
) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
private lateinit var centralSurfaces: CentralSurfaces
private lateinit var shadeViewController: ShadeViewController
@@ -288,7 +286,7 @@
// up, with unpredictable consequences.
if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&
shouldAnimateInKeyguard) {
- if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ if (!KeyguardShadeMigrationNssl.isEnabled) {
// Tracking this state should no longer be relevant, as the isInteractive
// check covers it
aodUiAnimationPlaying = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
index 22d0483..a2f5701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
@@ -16,15 +16,41 @@
package com.android.systemui.statusbar.pipeline.mobile.util
+import android.annotation.SuppressLint
+import android.content.Context
+import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
interface SubscriptionManagerProxy {
fun getDefaultDataSubscriptionId(): Int
+ fun isValidSubscriptionId(subId: Int): Boolean
+ suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo?
}
/** Injectable proxy class for [SubscriptionManager]'s static methods */
-class SubscriptionManagerProxyImpl @Inject constructor() : SubscriptionManagerProxy {
+class SubscriptionManagerProxyImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val subscriptionManager: SubscriptionManager,
+) : SubscriptionManagerProxy {
/** The system default data subscription id, or INVALID_SUBSCRIPTION_ID on error */
override fun getDefaultDataSubscriptionId() = SubscriptionManager.getDefaultDataSubscriptionId()
+
+ override fun isValidSubscriptionId(subId: Int): Boolean {
+ return SubscriptionManager.isValidSubscriptionId(subId)
+ }
+
+ @SuppressLint("MissingPermission")
+ override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? {
+ return withContext(backgroundDispatcher) {
+ subscriptionManager.getActiveSubscriptionInfo(subId)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index b614b6d..78954de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -35,13 +35,12 @@
import com.android.keyguard.KeyguardVisibilityHelper;
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -149,7 +148,6 @@
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
UserSwitchDialogController userSwitchDialogController,
- FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
super(view);
if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
@@ -163,7 +161,7 @@
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
screenOffAnimationController, /* animateYPos= */ false,
- featureFlags, /* logBuffer= */ null);
+ /* logBuffer= */ null);
mUserSwitchDialogController = userSwitchDialogController;
mUiEventLogger = uiEventLogger;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index dfe2686..770f441 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -39,7 +39,6 @@
import com.android.keyguard.dagger.KeyguardUserSwitcherScope;
import com.android.settingslib.drawable.CircleFramedDrawable;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -161,7 +160,6 @@
KeyguardStateController keyguardStateController,
SysuiStatusBarStateController statusBarStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- FeatureFlags featureFlags,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController) {
super(keyguardUserSwitcherView);
@@ -177,7 +175,7 @@
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
keyguardStateController, dozeParameters,
screenOffAnimationController, /* animateYPos= */ false,
- featureFlags, /* logBuffer= */ null);
+ /* logBuffer= */ null);
mBackground = new KeyguardUserSwitcherScrim(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 818ba95..3304b98 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -71,6 +71,7 @@
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule;
+import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule;
import dagger.Binds;
import dagger.Module;
@@ -81,7 +82,7 @@
import javax.inject.Named;
/** Dagger Module for code in the statusbar.policy package. */
-@Module(includes = { DeviceProvisioningRepositoryModule.class })
+@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class })
public interface StatusBarPolicyModule {
String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt
new file mode 100644
index 0000000..94ab58a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.policy.data.repository
+
+import android.app.NotificationManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.ZenModeController
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * A repository that holds information about the status and configuration of Zen Mode (or Do Not
+ * Disturb/DND Mode).
+ */
+interface ZenModeRepository {
+ val zenMode: Flow<Int>
+ val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?>
+}
+
+class ZenModeRepositoryImpl
+@Inject
+constructor(
+ private val zenModeController: ZenModeController,
+) : ZenModeRepository {
+ // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The
+ // conflatedCallbackFlows here should be replaced eventually, see:
+ // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw
+
+ override val zenMode: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : ZenModeController.Callback {
+ override fun onZenChanged(zen: Int) {
+ trySend(zen)
+ }
+ }
+ zenModeController.addCallback(callback)
+ trySend(zenModeController.zen)
+
+ awaitClose { zenModeController.removeCallback(callback) }
+ }
+
+ override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : ZenModeController.Callback {
+ override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) {
+ trySend(policy)
+ }
+ }
+ zenModeController.addCallback(callback)
+ trySend(zenModeController.consolidatedPolicy)
+
+ awaitClose { zenModeController.removeCallback(callback) }
+ }
+}
+
+@Module
+interface ZenModeRepositoryModule {
+ @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
new file mode 100644
index 0000000..ae31851
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.policy.domain.interactor
+
+import android.provider.Settings
+import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+
+/**
+ * An interactor that performs business logic related to the status and configuration of Zen Mode
+ * (or Do Not Disturb/DND Mode).
+ */
+class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) {
+ val isZenModeEnabled: Flow<Boolean> =
+ repository.zenMode
+ .map {
+ when (it) {
+ Settings.Global.ZEN_MODE_ALARMS -> true
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true
+ Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true
+ Settings.Global.ZEN_MODE_OFF -> false
+ else -> false
+ }
+ }
+ .distinctUntilChanged()
+
+ val areNotificationsHiddenInShade: Flow<Boolean> =
+ combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy ->
+ if (!dndEnabled) {
+ false
+ } else {
+ val showInNotificationList = policy?.showInNotificationList() ?: true
+ !showInNotificationList
+ }
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index fc414b6..2f2c4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -39,7 +39,6 @@
import com.android.app.animation.Interpolators
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -48,9 +47,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
@@ -96,7 +94,6 @@
wakeLockBuilder: WakeLock.Builder,
systemClock: SystemClock,
tempViewUiEventLogger: TemporaryViewUiEventLogger,
- private val featureFlags: FeatureFlags,
) :
TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
@@ -234,18 +231,14 @@
maybeGetAccessibilityFocus(newInfo, currentView)
// ---- Haptics ----
- if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
- vibratorHelper.performHapticFeedback(parent, newInfo.vibrationConstant)
- } else {
- newInfo.vibrationEffect?.let {
- vibratorHelper.vibrate(
- Process.myUid(),
- context.getApplicationContext().getPackageName(),
- it,
- newInfo.windowTitle,
- VIBRATION_ATTRIBUTES,
- )
- }
+ newInfo.vibrationEffect?.let {
+ vibratorHelper.vibrate(
+ Process.myUid(),
+ context.getApplicationContext().getPackageName(),
+ it,
+ newInfo.windowTitle,
+ VIBRATION_ATTRIBUTES,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 4449f8d0..7475388 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -17,13 +17,12 @@
package com.android.systemui.temporarydisplay.chipbar
import android.os.VibrationEffect
-import android.view.HapticFeedbackConstants
import android.view.View
import androidx.annotation.AttrRes
import com.android.internal.logging.InstanceId
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.res.R
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.temporarydisplay.ViewPriority
@@ -43,7 +42,6 @@
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
- val vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
val allowSwipeToDismiss: Boolean = false,
override val windowTitle: String,
override val wakeReason: String,
diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLog.kt b/packages/SystemUI/src/com/android/systemui/util/EventLog.kt
new file mode 100644
index 0000000..dc794cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/EventLog.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.util
+
+/**
+ * Testable wrapper around {@link android.util.EventLog}.
+ *
+ * Dagger can inject this wrapper into your classes. The implementation just proxies calls to the
+ * real EventLog.
+ *
+ * In tests, pass an instance of FakeEventLog, which allows you to examine the values passed to the
+ * various methods below.
+ */
+interface EventLog {
+ /** @see android.util.EventLog.writeEvent */
+ fun writeEvent(tag: Int, value: Int): Int
+
+ /** @see android.util.EventLog.writeEvent */
+ fun writeEvent(tag: Int, value: Long): Int
+
+ /** @see android.util.EventLog.writeEvent */
+ fun writeEvent(tag: Int, value: Float): Int
+
+ /** @see android.util.EventLog.writeEvent */
+ fun writeEvent(tag: Int, value: String): Int
+
+ /** @see android.util.EventLog.writeEvent */
+ fun writeEvent(tag: Int, vararg values: Any): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt b/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt
new file mode 100644
index 0000000..6fb1adc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util
+
+import javax.inject.Inject
+
+/** Default implementation of [com.android.systemui.util.EventLog]. */
+class EventLogImpl @Inject constructor() : EventLog {
+ override fun writeEvent(tag: Int, value: Int): Int =
+ android.util.EventLog.writeEvent(tag, value)
+
+ override fun writeEvent(tag: Int, value: Long): Int =
+ android.util.EventLog.writeEvent(tag, value)
+
+ override fun writeEvent(tag: Int, value: Float): Int =
+ android.util.EventLog.writeEvent(tag, value)
+
+ override fun writeEvent(tag: Int, value: String): Int =
+ android.util.EventLog.writeEvent(tag, value)
+
+ override fun writeEvent(tag: Int, vararg values: Any): Int =
+ android.util.EventLog.writeEvent(tag, *values)
+}
diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt
similarity index 71%
copy from core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
copy to packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt
index 2529807..ca0876c 100644
--- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl
+++ b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt
@@ -13,16 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.companion.virtual.camera;
-/**
- * Counterpart of ICameraDeviceSession for virtual camera.
- *
- * @hide
- */
-interface IVirtualCameraSession {
+package com.android.systemui.util
- void configureStream(int width, int height, int format);
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
- void close();
+@Module
+interface EventLogModule {
+ @SysUISingleton @Binds fun bindEventLog(eventLogImpl: EventLogImpl?): EventLog?
}
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index 97e43ad..d8e7cd6 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -26,11 +26,19 @@
import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
+import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
+import javax.inject.Provider
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.CoroutineStart
@@ -56,6 +64,7 @@
@Binds @Application fun bindAppResources(resources: Resources): Resources
@Binds @Main fun bindMainResources(resources: Resources): Resources
@Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
+ @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor
companion object {
@Provides
@@ -72,6 +81,19 @@
@Provides
fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher =
test.fakeBroadcastDispatcher
+
+ @Provides
+ fun provideBaseShadeInteractor(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
+ sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
+ ): BaseShadeInteractor {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 4d8768f..2e9b7e8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -127,7 +127,6 @@
withDeps.featureFlags.apply {
set(Flags.REGION_SAMPLING, false)
- set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
set(Flags.FACE_AUTH_REFACTOR, false)
}
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 1d8a664..6a08eea 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -21,7 +21,6 @@
import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT;
-import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeast;
@@ -182,7 +181,6 @@
mFakeFeatureFlags = new FakeFeatureFlags();
mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
- mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false);
mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false);
mController = new KeyguardClockSwitchController(
mView,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 22c75d8..146715d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -30,7 +30,6 @@
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -60,7 +59,6 @@
@Mock protected ScreenOffAnimationController mScreenOffAnimationController;
@Mock protected KeyguardLogger mKeyguardLogger;
@Mock protected KeyguardStatusViewController mControllerMock;
- @Mock protected FeatureFlags mFeatureFlags;
@Mock protected InteractionJankMonitor mInteractionJankMonitor;
@Mock protected ViewTreeObserver mViewTreeObserver;
@Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -91,7 +89,6 @@
mDozeParameters,
mScreenOffAnimationController,
mKeyguardLogger,
- mFeatureFlags,
mInteractionJankMonitor,
deps.getKeyguardInteractor(),
mKeyguardTransitionInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 776799e..2b41e080 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -3436,7 +3436,8 @@
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+ when(mUsbPortStatus.getComplianceWarnings())
+ .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
}
private Context getSpyContext() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index 67d6aa8..d8799e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -54,7 +54,7 @@
/**
* Tests for {@link android.view.accessibility.IWindowMagnificationConnection} retrieved from
- * {@link WindowMagnification}
+ * {@link Magnification}
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -86,7 +86,7 @@
private AccessibilityLogger mA11yLogger;
private IWindowMagnificationConnection mIWindowMagnificationConnection;
- private WindowMagnification mWindowMagnification;
+ private Magnification mMagnification;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@Before
@@ -98,16 +98,16 @@
return null;
}).when(mAccessibilityManager).setWindowMagnificationConnection(
any(IWindowMagnificationConnection.class));
- mWindowMagnification = new WindowMagnification(getContext(),
+ mMagnification = new Magnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class));
- mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
+ mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class));
- mWindowMagnification.requestWindowMagnificationConnection(true);
+ mMagnification.requestWindowMagnificationConnection(true);
assertNotNull(mIWindowMagnificationConnection);
mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback);
}
@@ -161,7 +161,7 @@
@Test
public void showMagnificationButton() throws RemoteException {
// magnification settings panel should not be showing
- assertFalse(mWindowMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
+ assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
@@ -195,8 +195,8 @@
testUserId, TEST_DISPLAY, testScale);
waitForIdleSync();
- assertTrue(mWindowMagnification.mUsersScales.contains(testUserId));
- assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
+ assertTrue(mMagnification.mUsersScales.contains(testUserId));
+ assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY),
(Float) testScale);
verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index d7b6602..c972feb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -65,7 +65,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class WindowMagnificationTest extends SysuiTestCase {
+public class MagnificationTest extends SysuiTestCase {
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@Mock
@@ -82,7 +82,7 @@
private SecureSettings mSecureSettings;
private CommandQueue mCommandQueue;
- private WindowMagnification mWindowMagnification;
+ private Magnification mMagnification;
private OverviewProxyListener mOverviewProxyListener;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
@@ -107,12 +107,12 @@
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
doAnswer(invocation -> {
- mWindowMagnification.mMagnificationSettingsControllerCallback
+ mMagnification.mMagnificationSettingsControllerCallback
.onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true);
return null;
}).when(mMagnificationSettingsController).toggleSettingsPanelVisibility();
doAnswer(invocation -> {
- mWindowMagnification.mMagnificationSettingsControllerCallback
+ mMagnification.mMagnificationSettingsControllerCallback
.onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false);
return null;
}).when(mMagnificationSettingsController).closeMagnificationSettings();
@@ -120,15 +120,15 @@
when(mWindowMagnificationController.isActivated()).thenReturn(true);
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
- mWindowMagnification = new WindowMagnification(getContext(),
+ mMagnification = new Magnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger);
- mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
- mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
+ mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController);
- mWindowMagnification.start();
+ mMagnification.start();
final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor =
ArgumentCaptor.forClass(OverviewProxyListener.class);
@@ -156,7 +156,7 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mWindowMagnifierCallback
+ mMagnification.mWindowMagnifierCallback
.onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
verify(mConnectionCallback).onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds);
@@ -169,7 +169,7 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mWindowMagnifierCallback
+ mMagnification.mWindowMagnifierCallback
.onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence);
verify(mConnectionCallback).onPerformScaleAction(
@@ -181,7 +181,7 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mWindowMagnifierCallback
+ mMagnification.mWindowMagnifierCallback
.onAccessibilityActionPerformed(TEST_DISPLAY);
verify(mConnectionCallback).onAccessibilityActionPerformed(TEST_DISPLAY);
@@ -192,14 +192,14 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY);
+ mMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY);
verify(mConnectionCallback).onMove(TEST_DISPLAY);
}
@Test
public void onClickSettingsButton_enabled_showPanelForWindowMode() {
- mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
+ mMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
waitForIdleSync();
verify(mMagnificationSettingsController).toggleSettingsPanelVisibility();
@@ -212,7 +212,7 @@
@Test
public void onSetMagnifierSize_delegateToMagnifier() {
final @MagnificationSize int index = MagnificationSize.SMALL;
- mWindowMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize(
+ mMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize(
TEST_DISPLAY, index);
waitForIdleSync();
@@ -225,7 +225,7 @@
@Test
public void onSetDiagonalScrolling_delegateToMagnifier() {
- mWindowMagnification.mMagnificationSettingsControllerCallback.onSetDiagonalScrolling(
+ mMagnification.mMagnificationSettingsControllerCallback.onSetDiagonalScrolling(
TEST_DISPLAY, /* enable= */ true);
waitForIdleSync();
@@ -235,7 +235,7 @@
@Test
public void onEditMagnifierSizeMode_windowActivated_delegateToMagnifier() {
when(mWindowMagnificationController.isActivated()).thenReturn(true);
- mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
+ mMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
TEST_DISPLAY, /* enable= */ true);
waitForIdleSync();
@@ -243,7 +243,7 @@
verify(mA11yLogger).log(
eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED));
- mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
+ mMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
TEST_DISPLAY, /* enable= */ false);
waitForIdleSync();
verify(mA11yLogger).log(
@@ -258,7 +258,7 @@
waitForIdleSync();
final float scale = 3.0f;
final boolean updatePersistence = false;
- mWindowMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale(
+ mMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale(
TEST_DISPLAY, scale, updatePersistence);
verify(mConnectionCallback).onPerformScaleAction(
@@ -274,7 +274,7 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
+ mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
waitForIdleSync();
@@ -292,7 +292,7 @@
mCommandQueue.requestWindowMagnificationConnection(true);
waitForIdleSync();
- mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
+ mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch(
TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
waitForIdleSync();
@@ -305,7 +305,7 @@
public void onSettingsPanelVisibilityChanged_windowActivated_delegateToMagnifier() {
when(mWindowMagnificationController.isActivated()).thenReturn(true);
final boolean shown = false;
- mWindowMagnification.mMagnificationSettingsControllerCallback
+ mMagnification.mMagnificationSettingsControllerCallback
.onSettingsPanelVisibilityChanged(TEST_DISPLAY, shown);
waitForIdleSync();
@@ -325,9 +325,9 @@
@Test
public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() {
final WindowMagnificationController mController = mock(WindowMagnificationController.class);
- mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
+ mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mController);
- mWindowMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
+ mMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY);
mOverviewProxyListener.onConnectionChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 87ab5b0..64ddbc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,7 +29,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -51,10 +54,12 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
+ @Mock private lateinit var tableLogger: TableLogBuffer
private val testUtils = SceneTestUtils(this)
private val testScope = testUtils.testScope
private val userRepository = FakeUserRepository()
+ private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var underTest: AuthenticationRepository
@@ -67,6 +72,8 @@
userRepository.setUserInfos(USER_INFOS)
runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
+ mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
underTest =
AuthenticationRepositoryImpl(
@@ -76,6 +83,7 @@
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
broadcastDispatcher = fakeBroadcastDispatcher,
+ mobileConnectionsRepository = mobileConnectionsRepository,
)
}
@@ -97,6 +105,11 @@
assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
assertThat(underTest.getAuthenticationMethod())
.isEqualTo(AuthenticationMethodModel.None)
+
+ currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin
+ mobileConnectionsRepository.isAnySimSecure.value = true
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim)
+ assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim)
}
@Test
@@ -157,8 +170,7 @@
userRepository.setSelectedUserInfo(USER_INFOS[1])
assertThat(values.last()).isTrue()
-
- }
+ }
private fun setSecurityModeAndDispatchBroadcast(
securityMode: KeyguardSecurityModel.SecurityMode,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 7439db2..56d3d26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -455,4 +455,22 @@
assertThat(hintedPinLength).isNull()
}
+
+ @Test
+ fun authenticate_withTooShortPassword() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ assertThat(
+ underTest.authenticate(
+ buildList {
+ repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ add("$time")
+ }
+ }
+ )
+ )
+ .isEqualTo(AuthenticationResult.SKIPPED)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
new file mode 100644
index 0000000..b391b5a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.bouncer.data.repository
+
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SimBouncerRepositoryTest : SysuiTestCase() {
+ @Mock lateinit var euiccManager: EuiccManager
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val fakeSubscriptionManagerProxy = FakeSubscriptionManagerProxy()
+ private val keyguardUpdateMonitorCallbacks = mutableListOf<KeyguardUpdateMonitorCallback>()
+
+ private lateinit var underTest: SimBouncerRepositoryImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(/* testClass = */ this)
+ whenever(keyguardUpdateMonitor.registerCallback(any())).thenAnswer {
+ val cb = it.arguments[0] as KeyguardUpdateMonitorCallback
+ keyguardUpdateMonitorCallbacks.add(cb)
+ }
+ whenever(keyguardUpdateMonitor.removeCallback(any())).thenAnswer {
+ keyguardUpdateMonitorCallbacks.remove(it.arguments[0])
+ }
+ underTest =
+ SimBouncerRepositoryImpl(
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = dispatcher,
+ resources = context.resources,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ subscriptionManager = fakeSubscriptionManagerProxy,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ euiccManager = euiccManager,
+ )
+ }
+
+ @Test
+ fun subscriptionId() =
+ testScope.runTest {
+ val subscriptionId =
+ emitSubscriptionIdAndCollectLastValue(underTest.subscriptionId, subId = 2)
+ assertThat(subscriptionId).isEqualTo(2)
+ }
+
+ @Test
+ fun activeSubscriptionInfo() =
+ testScope.runTest {
+ fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2)
+ val activeSubscriptionInfo =
+ emitSubscriptionIdAndCollectLastValue(underTest.activeSubscriptionInfo, subId = 2)
+
+ assertThat(activeSubscriptionInfo?.subscriptionId).isEqualTo(2)
+ }
+
+ @Test
+ fun isLockedEsim_initialValue_isNull() =
+ testScope.runTest {
+ val isLockedEsim by collectLastValue(underTest.isLockedEsim)
+ assertThat(isLockedEsim).isNull()
+ }
+
+ @Test
+ fun isLockedEsim() =
+ testScope.runTest {
+ whenever(euiccManager.isEnabled).thenReturn(true)
+ fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = true)
+ val isLockedEsim =
+ emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2)
+ assertThat(isLockedEsim).isTrue()
+ }
+
+ @Test
+ fun isLockedEsim_notEmbedded() =
+ testScope.runTest {
+ fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = false)
+ val isLockedEsim =
+ emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2)
+ assertThat(isLockedEsim).isFalse()
+ }
+
+ @Test
+ fun isSimPukLocked() =
+ testScope.runTest {
+ val isSimPukLocked =
+ emitSubscriptionIdAndCollectLastValue(
+ underTest.isSimPukLocked,
+ subId = 2,
+ isSimPuk = true
+ )
+ assertThat(isSimPukLocked).isTrue()
+ }
+
+ @Test
+ fun setSimPukUserInput() {
+ val pukCode = "00000000"
+ val pinCode = "1234"
+ underTest.setSimPukUserInput(pukCode, pinCode)
+ assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode)
+ assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode)
+ }
+
+ @Test
+ fun setSimPukUserInput_nullPuk() {
+ val pukCode = null
+ val pinCode = "1234"
+ underTest.setSimPukUserInput(pukCode, pinCode)
+ assertThat(underTest.simPukInputModel.enteredSimPuk).isNull()
+ assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode)
+ }
+
+ @Test
+ fun setSimPukUserInput_nullPin() {
+ val pukCode = "00000000"
+ val pinCode = null
+ underTest.setSimPukUserInput(pukCode, pinCode)
+ assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode)
+ assertThat(underTest.simPukInputModel.enteredSimPin).isNull()
+ }
+
+ @Test
+ fun setSimPukUserInput_nullCodes() {
+ underTest.setSimPukUserInput()
+ assertThat(underTest.simPukInputModel.enteredSimPuk).isNull()
+ assertThat(underTest.simPukInputModel.enteredSimPin).isNull()
+ }
+
+ @Test
+ fun setSimPinVerificationErrorMessage() =
+ testScope.runTest {
+ val errorMsg = "error"
+ underTest.setSimVerificationErrorMessage(errorMsg)
+ val msg by collectLastValue(underTest.errorDialogMessage)
+ assertThat(msg).isEqualTo(errorMsg)
+ }
+
+ /** Emits a new sim card state and collects the last value of the flow argument. */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun <T> TestScope.emitSubscriptionIdAndCollectLastValue(
+ flow: Flow<T>,
+ subId: Int = 1,
+ isSimPuk: Boolean = false
+ ): T? {
+ val value by collectLastValue(flow)
+ runCurrent()
+ val simState =
+ if (isSimPuk) {
+ TelephonyManager.SIM_STATE_PUK_REQUIRED
+ } else {
+ TelephonyManager.SIM_STATE_PIN_REQUIRED
+ }
+ whenever(keyguardUpdateMonitor.getNextSubIdForState(anyInt())).thenReturn(-1)
+ whenever(keyguardUpdateMonitor.getNextSubIdForState(simState)).thenReturn(subId)
+ keyguardUpdateMonitorCallbacks.forEach {
+ it.onSimStateChanged(subId, /* slotId= */ 0, simState)
+ }
+ runCurrent()
+ return value
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 6ead0e9..50d2fd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -88,6 +88,19 @@
}
@Test
+ fun pinAuthMethod_sim_skipsAuthentication() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ runCurrent()
+
+ // We rely on TelephonyManager to authenticate the sim card.
+ // Additionally, authenticating the sim card does not unlock the device.
+ // Thus, when auth method is sim, we expect to skip here.
+ assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+ .isEqualTo(AuthenticationResult.SKIPPED)
+ }
+
+ @Test
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
@@ -159,6 +172,19 @@
underTest.resetMessage()
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
+ // Too short input.
+ assertThat(
+ underTest.authenticate(
+ buildList {
+ repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ add("$time")
+ }
+ }
+ )
+ )
+ .isEqualTo(AuthenticationResult.SKIPPED)
+ assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD)
+
// Correct input.
assertThat(underTest.authenticate("password".toList()))
.isEqualTo(AuthenticationResult.SUCCEEDED)
@@ -291,6 +317,14 @@
assertThat(imeHiddenEvent).isNotNull()
}
+ @Test
+ fun intentionalUserInputEvent_registersTouchEvent() =
+ testScope.runTest {
+ assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ underTest.onIntentionalUserInput()
+ assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ }
+
private fun assertTryAgainMessage(
message: String?,
time: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
new file mode 100644
index 0000000..8c53c0e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.bouncer.domain.interactor
+
+import android.content.res.Resources
+import android.telephony.PinResult
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class SimBouncerInteractorTest : SysuiTestCase() {
+ @Mock lateinit var telephonyManager: TelephonyManager
+ @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var euiccManager: EuiccManager
+
+ private val utils = SceneTestUtils(this)
+ private val bouncerSimRepository = FakeSimBouncerRepository()
+ private val resources: Resources = context.resources
+ private val testScope = utils.testScope
+
+ private lateinit var underTest: SimBouncerInteractor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ SimBouncerInteractor(
+ context,
+ testScope.backgroundScope,
+ utils.testDispatcher,
+ bouncerSimRepository,
+ telephonyManager,
+ resources,
+ keyguardUpdateMonitor,
+ euiccManager,
+ utils.mobileConnectionsRepository,
+ )
+ }
+
+ @Test
+ fun getDefaultMessage() {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setDisplayName("sim").build()
+ )
+ whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions))
+ }
+
+ @Test
+ fun getDefaultMessage_isPuk() {
+ bouncerSimRepository.setSimPukLocked(true)
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setDisplayName("sim").build()
+ )
+ whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint))
+ }
+
+ @Test
+ fun getDefaultMessage_isEsimLocked() {
+ bouncerSimRepository.setLockedEsim(true)
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setDisplayName("sim").build()
+ )
+ whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+ val msg = resources.getString(R.string.kg_sim_pin_instructions)
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_sim_lock_esim_instructions, msg))
+ }
+
+ @Test
+ fun getDefaultMessage_multipleSims() {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setDisplayName("sim").build()
+ )
+ whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions_multi, "sim"))
+ }
+
+ @Test
+ fun getDefaultMessage_multipleSims_isPuk() {
+ bouncerSimRepository.setSimPukLocked(true)
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setDisplayName("sim").build()
+ )
+ whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint_multi, "sim"))
+ }
+
+ @Test
+ fun getDefaultMessage_multipleSims_emptyDisplayName() {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build())
+ whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions))
+ }
+
+ @Test
+ fun getDefaultMessage_multipleSims_emptyDisplayName_isPuk() {
+ bouncerSimRepository.setSimPukLocked(true)
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build())
+ whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+ assertThat(underTest.getDefaultMessage())
+ .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint))
+ }
+
+ @Test
+ fun resetSimPukUserInput() {
+ bouncerSimRepository.setSimPukUserInput("00000000", "1234")
+
+ assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isEqualTo("00000000")
+ assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isEqualTo("1234")
+
+ underTest.resetSimPukUserInput()
+
+ assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isNull()
+ assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isNull()
+ }
+
+ @Test
+ fun disableEsim() =
+ testScope.runTest {
+ val portIndex = 1
+ bouncerSimRepository.setActiveSubscriptionInfo(
+ SubscriptionInfo.Builder().setPortIndex(portIndex).build()
+ )
+
+ underTest.disableEsim()
+ runCurrent()
+
+ verify(euiccManager)
+ .switchToSubscription(
+ eq(INVALID_SUBSCRIPTION_ID),
+ eq(portIndex),
+ ArgumentMatchers.any()
+ )
+ }
+
+ @Test
+ fun verifySimPin() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(false)
+ whenever(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager)
+ whenever(telephonyManager.supplyIccLockPin(anyString()))
+ .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
+
+ val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
+ runCurrent()
+ assertThat(msg).isNull()
+
+ verify(keyguardUpdateMonitor).reportSimUnlocked(1)
+ }
+
+ @Test
+ fun verifySimPin_incorrect_oneRemainingAttempt() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(false)
+ whenever(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager)
+ whenever(telephonyManager.supplyIccLockPin(anyString()))
+ .thenReturn(
+ PinResult(
+ PinResult.PIN_RESULT_TYPE_INCORRECT,
+ 1,
+ )
+ )
+
+ val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
+ runCurrent()
+
+ assertThat(msg).isNull()
+ val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
+ assertThat(errorDialogMessage)
+ .isEqualTo(
+ "Enter SIM PIN. You have 1 remaining attempt before you must contact" +
+ " your carrier to unlock your device."
+ )
+ }
+
+ @Test
+ fun verifySimPin_incorrect_threeRemainingAttempts() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(false)
+ whenever(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager)
+ whenever(telephonyManager.supplyIccLockPin(anyString()))
+ .thenReturn(
+ PinResult(
+ PinResult.PIN_RESULT_TYPE_INCORRECT,
+ 3,
+ )
+ )
+
+ val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ runCurrent()
+
+ assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
+ }
+
+ @Test
+ fun verifySimPin_notCorrectLength_tooShort() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(false)
+
+ val msg = underTest.verifySim(listOf(0))
+
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ }
+
+ @Test
+ fun verifySimPin_notCorrectLength_tooLong() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(false)
+
+ val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ }
+
+ @Test
+ fun verifySimPuk() =
+ testScope.runTest {
+ whenever(telephonyManager.createForSubscriptionId(anyInt()))
+ .thenReturn(telephonyManager)
+ whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
+ .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(true)
+
+ var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
+
+ msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
+
+ msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ assertThat(msg).isNull()
+
+ runCurrent()
+ verify(keyguardUpdateMonitor).reportSimUnlocked(1)
+ }
+
+ @Test
+ fun verifySimPuk_inputTooShort() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(true)
+ val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
+ }
+
+ @Test
+ fun verifySimPuk_pinNotCorrectLength() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(true)
+
+ underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+
+ val msg = underTest.verifySim(listOf(0, 0, 0))
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+ }
+
+ @Test
+ fun verifySimPuk_confirmedPinDoesNotMatch() =
+ testScope.runTest {
+ bouncerSimRepository.setSubscriptionId(1)
+ bouncerSimRepository.setSimPukLocked(true)
+
+ underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+ underTest.verifySim(listOf(0, 0, 0, 0))
+
+ val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+ assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
+ }
+
+ @Test
+ fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
+ bouncerSimRepository.setSimVerificationErrorMessage("abc")
+ assertThat(bouncerSimRepository.errorDialogMessage.value).isNotNull()
+
+ underTest.onErrorDialogDismissed()
+
+ assertThat(bouncerSimRepository.errorDialogMessage.value).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index cfcb545..63c992b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -48,6 +48,8 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true),
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationMethod = AuthenticationMethodModel.Pin,
)
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index f4346b5..75d6a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -233,6 +233,7 @@
AuthenticationMethodModel.Pin,
AuthenticationMethodModel.Password,
AuthenticationMethodModel.Pattern,
+ AuthenticationMethodModel.Sim,
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c498edf..9b1e958 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -78,12 +78,28 @@
lockDeviceAndOpenPasswordBouncer()
assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
- assertThat(password).isEqualTo("")
+ assertThat(password).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password)
}
@Test
+ fun onHidden_resetsPasswordInputAndMessage() =
+ testScope.runTest {
+ val message by collectLastValue(bouncerViewModel.message)
+ val password by collectLastValue(underTest.password)
+ lockDeviceAndOpenPasswordBouncer()
+
+ underTest.onPasswordInputChanged("password")
+ assertThat(message?.text).isNotEqualTo(ENTER_YOUR_PASSWORD)
+ assertThat(password).isNotEmpty()
+
+ underTest.onHidden()
+ assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
+ assertThat(password).isEmpty()
+ }
+
+ @Test
fun onPasswordInputChanged() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
@@ -121,7 +137,7 @@
underTest.onPasswordInputChanged("wrong")
underTest.onAuthenticateKeyPressed()
- assertThat(password).isEqualTo("")
+ assertThat(password).isEmpty()
assertThat(message?.text).isEqualTo(WRONG_PASSWORD)
}
@@ -134,14 +150,13 @@
AuthenticationMethodModel.Password
)
utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
- underTest.onShown()
- // Enter nothing.
+ switchToScene(SceneKey.Bouncer)
+
+ // No input entered.
underTest.onAuthenticateKeyPressed()
- assertThat(password).isEqualTo("")
+ assertThat(password).isEmpty()
assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD)
}
@@ -182,32 +197,33 @@
assertThat(password).isEqualTo("password")
// The user doesn't confirm the password, but navigates back to the lockscreen instead.
- sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ switchToScene(SceneKey.Lockscreen)
// The user navigates to the bouncer again.
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
- underTest.onShown()
+ switchToScene(SceneKey.Bouncer)
// Ensure the previously-entered password is not shown.
assertThat(password).isEmpty()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
+ private fun TestScope.switchToScene(toScene: SceneKey) {
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
+ val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
+ val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+ sceneInteractor.changeScene(SceneModel(toScene), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(toScene), "reason")
+ if (bouncerShown) underTest.onShown()
+ if (bouncerHidden) underTest.onHidden()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(SceneModel(toScene))
+ }
+
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
- assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
- .isEqualTo(SceneModel(SceneKey.Bouncer))
- underTest.onShown()
- runCurrent()
+ switchToScene(SceneKey.Bouncer)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 3f5ddba..125fe68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -373,15 +373,23 @@
)
}
+ private fun TestScope.switchToScene(toScene: SceneKey) {
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
+ val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
+ val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+ sceneInteractor.changeScene(SceneModel(toScene), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(toScene), "reason")
+ if (bouncerShown) underTest.onShown()
+ if (bouncerHidden) underTest.onHidden()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(SceneModel(toScene))
+ }
+
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
- assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
- .isEqualTo(SceneModel(SceneKey.Bouncer))
- underTest.onShown()
- runCurrent()
+ switchToScene(SceneKey.Bouncer)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 7a9cb6c..c30e405 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -63,6 +63,8 @@
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationMethod = AuthenticationMethodModel.Pin,
)
@Before
@@ -74,49 +76,77 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
- underTest.onShown()
+ lockDeviceAndOpenPinBouncer()
assertThat(message?.text).ignoringCase().isEqualTo(ENTER_YOUR_PIN)
assertThat(pin).isEmpty()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pin)
}
@Test
+ fun simBouncerViewModel_simAreaIsVisible() =
+ testScope.runTest {
+ val underTest =
+ PinBouncerViewModel(
+ applicationContext = context,
+ viewModelScope = testScope.backgroundScope,
+ interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationMethod = AuthenticationMethodModel.Sim,
+ )
+
+ assertThat(underTest.isSimAreaVisible).isTrue()
+ }
+
+ @Test
+ fun onErrorDialogDismissed_clearsDialogMessage() =
+ testScope.runTest {
+ val dialogMessage by collectLastValue(underTest.errorDialogMessage)
+ utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+ assertThat(dialogMessage).isEqualTo("abc")
+
+ underTest.onErrorDialogDismissed()
+
+ assertThat(dialogMessage).isNull()
+ }
+
+ @Test
+ fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() =
+ testScope.runTest {
+ val underTest =
+ PinBouncerViewModel(
+ applicationContext = context,
+ viewModelScope = testScope.backgroundScope,
+ interactor = bouncerInteractor,
+ isInputEnabled = MutableStateFlow(true).asStateFlow(),
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationMethod = AuthenticationMethodModel.Sim,
+ )
+ utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+
+ assertThat(hintedPinLength).isNull()
+ }
+
+ @Test
fun onPinButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- underTest.onShown()
- runCurrent()
+ lockDeviceAndOpenPinBouncer()
underTest.onPinButtonClicked(1)
assertThat(message?.text).isEmpty()
assertThat(pin).containsExactly(1)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
lockDeviceAndOpenPinBouncer()
@@ -128,7 +158,6 @@
assertThat(message?.text).isEmpty()
assertThat(pin).isEmpty()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
@@ -176,9 +205,7 @@
collectLastValue(authenticationInteractor.authenticationChallengeResult)
lockDeviceAndOpenPinBouncer()
- FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
- underTest.onPinButtonClicked(digit)
- }
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
underTest.onAuthenticateButtonClicked()
@@ -226,9 +253,7 @@
assertThat(authResult).isFalse()
// Enter the correct PIN:
- FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
- underTest.onPinButtonClicked(digit)
- }
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
assertThat(message?.text).isEmpty()
underTest.onAuthenticateButtonClicked()
@@ -244,9 +269,7 @@
collectLastValue(authenticationInteractor.authenticationChallengeResult)
lockDeviceAndOpenPinBouncer()
- FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
- underTest.onPinButtonClicked(digit)
- }
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
assertThat(authResult).isTrue()
}
@@ -275,31 +298,21 @@
@Test
fun onShown_againAfterSceneChange_resetsPin() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.desiredScene)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
lockDeviceAndOpenPinBouncer()
// The user types a PIN.
- FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
- underTest.onPinButtonClicked(digit)
- }
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
assertThat(pin).isNotEmpty()
// The user doesn't confirm the PIN, but navigates back to the lockscreen instead.
- sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ switchToScene(SceneKey.Lockscreen)
// The user navigates to the bouncer again.
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-
- underTest.onShown()
+ switchToScene(SceneKey.Bouncer)
// Ensure the previously-entered PIN is not shown.
assertThat(pin).isEmpty()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
@Test
@@ -366,16 +379,23 @@
assertThat(isAnimationEnabled).isTrue()
}
+ private fun TestScope.switchToScene(toScene: SceneKey) {
+ val currentScene by collectLastValue(sceneInteractor.desiredScene)
+ val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer
+ val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer
+ sceneInteractor.changeScene(SceneModel(toScene), "reason")
+ sceneInteractor.onSceneChanged(SceneModel(toScene), "reason")
+ if (bouncerShown) underTest.onShown()
+ if (bouncerHidden) underTest.onHidden()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(SceneModel(toScene))
+ }
+
private fun TestScope.lockDeviceAndOpenPinBouncer() {
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.deviceEntryRepository.setUnlocked(false)
- sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
- sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
-
- assertThat(collectLastValue(sceneInteractor.desiredScene).invoke())
- .isEqualTo(SceneModel(SceneKey.Bouncer))
- underTest.onShown()
- runCurrent()
+ switchToScene(SceneKey.Bouncer)
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index abd9f28..0004f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -90,6 +90,16 @@
}
@Test
+ fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
+ testScope.runTest {
+ utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ utils.deviceEntryRepository.setUnlocked(true)
+
+ val isUnlocked by collectLastValue(underTest.isUnlocked)
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
fun isDeviceEntered_onLockscreenWithSwipe_isFalse() =
testScope.runTest {
val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index 7fb0dd5..bc4c237 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -413,6 +413,13 @@
)
transitionRepository.sendTransitionStep(
TransitionStep(
+ transitionState = TransitionState.CANCELED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.GONE,
to = KeyguardState.AOD,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 29b546b..4f7d944 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -26,13 +26,14 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertEquals
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -136,7 +137,7 @@
@Test
fun startedKeyguardStateTests() = testScope.runTest {
- val finishedSteps by collectValues(underTest.startedKeyguardState)
+ val startedStates by collectValues(underTest.startedKeyguardState)
runCurrent()
val steps = mutableListOf<TransitionStep>()
@@ -153,7 +154,7 @@
runCurrent()
}
- assertThat(finishedSteps).isEqualTo(listOf(OFF, PRIMARY_BOUNCER, AOD, GONE))
+ assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
}
@Test
@@ -162,12 +163,12 @@
val steps = mutableListOf<TransitionStep>()
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
steps.forEach {
@@ -175,7 +176,9 @@
runCurrent()
}
- assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+ // Ignore the default state.
+ assertThat(finishedSteps.subList(1, finishedSteps.size))
+ .isEqualTo(listOf(steps[2], steps[5]))
}
@Test
@@ -650,6 +653,81 @@
))
}
+ @Test
+ fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest {
+ val finishedStates by collectValues(underTest.finishedKeyguardState)
+
+ // We default FINISHED in LOCKSCREEN.
+ assertEquals(listOf(
+ LOCKSCREEN
+ ), finishedStates)
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
+ )
+
+ // We're FINISHED in AOD.
+ assertEquals(listOf(
+ LOCKSCREEN,
+ AOD,
+ ), finishedStates)
+
+ // Transition back to LOCKSCREEN.
+ sendSteps(
+ TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
+ )
+
+ // We're FINISHED in LOCKSCREEN.
+ assertEquals(listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ), finishedStates)
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ )
+
+ // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
+ // LOCKSCREEN.
+ assertEquals(listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ), finishedStates)
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
+ )
+
+ // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
+ assertEquals(listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ), finishedStates)
+
+ sendSteps(
+ TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
+ TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
+ TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
+ )
+
+ // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
+ // LOCKSCREEN after the cancellation.
+ assertEquals(listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ LOCKSCREEN,
+ ), finishedStates)
+ }
+
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
repository.sendTransitionStep(it)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index c292102..bf23bf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -618,11 +618,26 @@
@Test
fun dozingToLockscreenCannotBeInterruptedByDreaming() =
testScope.runTest {
+ transitionRepository.sendTransitionSteps(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.DOZING,
+ testScheduler
+ )
// GIVEN a prior transition has started to LOCKSCREEN
transitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.DOZING,
to = KeyguardState.LOCKSCREEN,
+ value = 0f,
+ transitionState = TransitionState.STARTED,
+ ownerName = "KeyguardTransitionScenariosTest",
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
value = 0.5f,
transitionState = TransitionState.RUNNING,
ownerName = "KeyguardTransitionScenariosTest",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index c02add1..03a1f7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -39,6 +39,7 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Spy
@@ -120,6 +121,9 @@
@Test
fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() =
testScope.runTest {
+ runCurrent()
+ reset(fakeLightRevealScrimRepository)
+
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
transitionState = TransitionState.STARTED,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 43d70ad..76c2589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.ui.view.layout.items.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
-import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
@@ -49,6 +48,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.Optional
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -60,7 +60,7 @@
@Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection
@Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
@Mock
- private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
+ private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection>
@Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
@Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
@Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
@@ -98,7 +98,7 @@
fun replaceViews() {
val constraintLayout = ConstraintLayout(context, null)
underTest.replaceViews(null, constraintLayout)
- underTest.sections.forEach { verify(it).addViews(constraintLayout) }
+ underTest.sections.forEach { verify(it)?.addViews(constraintLayout) }
}
@Test
@@ -110,7 +110,7 @@
val constraintLayout = ConstraintLayout(context, null)
underTest.replaceViews(prevBlueprint, constraintLayout)
underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach {
- verify(it, never()).addViews(constraintLayout)
+ verify(it, never())?.addViews(constraintLayout)
}
verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout)
@@ -121,6 +121,6 @@
fun applyConstraints() {
val cs = ConstraintSet()
underTest.applyConstraints(cs)
- underTest.sections.forEach { verify(it).applyConstraints(cs) }
+ underTest.sections.forEach { verify(it)?.applyConstraints(cs) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index a2eb5ef..db7c987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -35,7 +35,7 @@
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -50,6 +50,7 @@
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -74,7 +75,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class MediaHierarchyManagerTest : SysuiTestCase() {
@Mock private lateinit var lockHost: MediaHost
@@ -91,6 +92,7 @@
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
@Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock lateinit var logger: MediaViewLogger
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@@ -101,12 +103,12 @@
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
private lateinit var mediaHierarchyManager: MediaHierarchyManager
+ private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
private val communalInteractor =
CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
- private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
@@ -122,6 +124,8 @@
testableLooper = TestableLooper.get(this)
fakeHandler = FakeHandler(testableLooper.looper)
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
+ isQsBypassingShade = MutableStateFlow(false)
+ whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
mediaHierarchyManager =
MediaHierarchyManager(
context,
@@ -135,7 +139,7 @@
communalInteractor,
configurationController,
wakefulnessLifecycle,
- notifPanelEvents,
+ shadeInteractor,
settings,
fakeHandler,
testScope.backgroundScope,
@@ -430,17 +434,21 @@
assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
- notifPanelEvents.notifyExpandImmediateChange(true)
- goToLockscreen()
- enterGuidedTransformation()
- whenever(lockHost.visible).thenReturn(true)
- whenever(qsHost.visible).thenReturn(true)
- whenever(qqsHost.visible).thenReturn(true)
+ fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() =
+ testScope.runTest {
+ runCurrent()
+ isQsBypassingShade.value = true
+ runCurrent()
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(true)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
- assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
- }
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+ }
@Test
fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_active() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 83145bd..3be50b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -35,15 +35,13 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.statusbar.IUndoMediaTransferCallback
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.media.taptotransfer.MediaTttFlags
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -113,7 +111,6 @@
private lateinit var uiEventLogger: MediaTttSenderUiEventLogger
private lateinit var tempViewUiEventLogger: TemporaryViewUiEventLogger
private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay)
- private val featureFlags = FakeFeatureFlags()
@Before
fun setUp() {
@@ -163,9 +160,7 @@
fakeWakeLockBuilder,
fakeClock,
tempViewUiEventLogger,
- featureFlags
)
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
chipbarCoordinator.start()
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 5e4c954..1f7a029 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -16,6 +16,7 @@
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
@@ -61,6 +62,8 @@
@Mock private lateinit var configuration: Configuration
@Mock private lateinit var pagedTileLayout: PagedTileLayout
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+
private lateinit var controller: QSPanelController
private val testableResources: TestableResources = mContext.orCreateTestableResources
@@ -96,7 +99,8 @@
brightnessSliderFactory,
falsingManager,
statusBarKeyguardViewManager,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 555484c..8acde36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -20,6 +20,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -42,6 +43,8 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
+ private val sceneContainerFlags = FakeSceneContainerFlags()
+
private lateinit var controller: QuickStatusBarHeaderController
@Before
@@ -51,7 +54,11 @@
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
- controller = QuickStatusBarHeaderController(view, quickQSPanelController)
+ controller = QuickStatusBarHeaderController(
+ view,
+ quickQSPanelController,
+ sceneContainerFlags,
+ )
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
new file mode 100644
index 0000000..c1c0126
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -0,0 +1,294 @@
+/*
+ * 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.qs.ui.adapter
+
+import android.os.Bundle
+import android.view.View
+import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.QSImpl
+import com.android.systemui.qs.dagger.QSComponent
+import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSSceneAdapterImplTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val qsImplProvider =
+ object : Provider<QSImpl> {
+ val impls = mutableListOf<QSImpl>()
+
+ override fun get(): QSImpl {
+ return mock<QSImpl> {
+ lateinit var _view: View
+ whenever(onComponentCreated(any(), any())).then {
+ _view = it.getArgument<QSComponent>(0).getRootView()
+ Unit
+ }
+ whenever(view).thenAnswer { _view }
+ }
+ .also { impls.add(it) }
+ }
+ }
+
+ private val qsSceneComponentFactory =
+ object : QSSceneComponent.Factory {
+ val components = mutableListOf<QSSceneComponent>()
+
+ override fun create(rootView: View): QSSceneComponent {
+ return mock<QSSceneComponent> { whenever(this.getRootView()).thenReturn(rootView) }
+ .also { components.add(it) }
+ }
+ }
+
+ private val mockAsyncLayoutInflater =
+ mock<AsyncLayoutInflater>() {
+ whenever(inflate(anyInt(), nullable(), any())).then { invocation ->
+ val mockView = mock<View>()
+ invocation
+ .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2)
+ .onInflateFinished(
+ mockView,
+ invocation.getArgument(0),
+ invocation.getArgument(1),
+ )
+ }
+ }
+
+ private val underTest =
+ QSSceneAdapterImpl(
+ qsSceneComponentFactory,
+ qsImplProvider,
+ testDispatcher,
+ testScope.backgroundScope,
+ { mockAsyncLayoutInflater },
+ )
+
+ @Test
+ fun inflate() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ assertThat(qsImpl).isNull()
+
+ underTest.inflate(context)
+ runCurrent()
+
+ assertThat(qsImpl).isNotNull()
+ assertThat(qsImpl).isSameInstanceAs(qsImplProvider.impls[0])
+ val inOrder = inOrder(qsImpl!!)
+ inOrder.verify(qsImpl!!).onCreate(nullable())
+ inOrder
+ .verify(qsImpl!!)
+ .onComponentCreated(
+ eq(qsSceneComponentFactory.components[0]),
+ any(),
+ )
+ }
+
+ @Test
+ fun initialState_closed() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ with(qsImpl!!) {
+ verify(this).setQsVisible(false)
+ verify(this)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
+ verify(this).setListening(false)
+ verify(this).setExpanded(false)
+ verify(this)
+ .setTransitionToFullShadeProgress(
+ /* isTransitioningToFullShade= */ false,
+ /* qsTransitionFraction= */ 1f,
+ /* qsSquishinessFraction = */ 1f,
+ )
+ }
+ }
+
+ @Test
+ fun state_qqs() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+ clearInvocations(qsImpl!!)
+
+ underTest.setState(QSSceneAdapter.State.QQS)
+ with(qsImpl!!) {
+ verify(this).setQsVisible(true)
+ verify(this)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
+ verify(this).setListening(true)
+ verify(this).setExpanded(true)
+ verify(this)
+ .setTransitionToFullShadeProgress(
+ /* isTransitioningToFullShade= */ false,
+ /* qsTransitionFraction= */ 1f,
+ /* qsSquishinessFraction = */ 1f,
+ )
+ }
+ }
+
+ @Test
+ fun state_qs() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+ clearInvocations(qsImpl!!)
+
+ underTest.setState(QSSceneAdapter.State.QS)
+ with(qsImpl!!) {
+ verify(this).setQsVisible(true)
+ verify(this)
+ .setQsExpansion(
+ /* expansion= */ 1f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ 1f,
+ )
+ verify(this).setListening(true)
+ verify(this).setExpanded(true)
+ verify(this)
+ .setTransitionToFullShadeProgress(
+ /* isTransitioningToFullShade= */ false,
+ /* qsTransitionFraction= */ 1f,
+ /* qsSquishinessFraction = */ 1f,
+ )
+ }
+ }
+
+ @Test
+ fun customizing_QS() =
+ testScope.runTest {
+ val customizing by collectLastValue(underTest.isCustomizing)
+
+ underTest.inflate(context)
+ runCurrent()
+ underTest.setState(QSSceneAdapter.State.QS)
+
+ assertThat(customizing).isFalse()
+
+ underTest.setCustomizerShowing(true)
+ assertThat(customizing).isTrue()
+
+ underTest.setCustomizerShowing(false)
+ assertThat(customizing).isFalse()
+ }
+
+ @Test
+ fun customizing_moveToQQS_stopCustomizing() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+ underTest.setState(QSSceneAdapter.State.QS)
+ underTest.setCustomizerShowing(true)
+
+ underTest.setState(QSSceneAdapter.State.QQS)
+ runCurrent()
+ verify(qsImpl!!).closeCustomizerImmediately()
+ }
+
+ @Test
+ fun customizing_moveToClosed_stopCustomizing() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+ underTest.setState(QSSceneAdapter.State.QS)
+ underTest.setCustomizerShowing(true)
+ runCurrent()
+
+ underTest.setState(QSSceneAdapter.State.CLOSED)
+ verify(qsImpl!!).closeCustomizerImmediately()
+ }
+
+ @Test
+ fun reinflation_previousStateDestroyed() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+ val oldQsImpl = qsImpl!!
+
+ underTest.inflate(context)
+ runCurrent()
+ val newQSImpl = qsImpl!!
+
+ assertThat(oldQsImpl).isNotSameInstanceAs(newQSImpl)
+ val inOrder = inOrder(oldQsImpl, newQSImpl)
+ val bundleArgCaptor = argumentCaptor<Bundle>()
+
+ inOrder.verify(oldQsImpl).onSaveInstanceState(capture(bundleArgCaptor))
+ inOrder.verify(oldQsImpl).onDestroy()
+ assertThat(newQSImpl).isSameInstanceAs(qsImplProvider.impls[1])
+ inOrder.verify(newQSImpl).onCreate(nullable())
+ inOrder
+ .verify(newQSImpl)
+ .onComponentCreated(
+ qsSceneComponentFactory.components[1],
+ bundleArgCaptor.value,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d582b9e..ef129c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -23,9 +23,12 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -53,6 +56,7 @@
private val sceneInteractor = utils.sceneInteractor()
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() }
private var mobileIconsViewModel: MobileIconsViewModel =
MobileIconsViewModel(
@@ -96,6 +100,7 @@
sceneInteractor = sceneInteractor,
),
shadeHeaderViewModel = shadeHeaderViewModel,
+ qsSceneAdapter = qsFlexiglassAdapter,
)
}
@@ -124,4 +129,33 @@
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
+
+ @Test
+ fun destinationsNotCustomizing() =
+ testScope.runTest {
+ val destinations by collectLastValue(underTest.destinationScenes)
+ qsFlexiglassAdapter.setCustomizing(false)
+
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ UserAction.Back to SceneModel(SceneKey.Shade),
+ UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+ )
+ )
+ }
+
+ @Test
+ fun destinationsCustomizing() =
+ testScope.runTest {
+ val destinations by collectLastValue(underTest.destinationScenes)
+ qsFlexiglassAdapter.setCustomizing(true)
+
+ assertThat(destinations)
+ .isEqualTo(
+ mapOf(
+ UserAction.Back to SceneModel(SceneKey.QuickSettings),
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index d1db9c1..6a054cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -20,6 +20,7 @@
import android.telecom.TelecomManager
import android.telephony.TelephonyManager
+import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
@@ -39,6 +40,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -182,6 +184,8 @@
private var bouncerSceneJob: Job? = null
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() })
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -232,6 +236,7 @@
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
+ qsSceneAdapter = qsFlexiglassAdapter,
)
utils.deviceEntryRepository.setUnlocked(false)
@@ -251,6 +256,8 @@
falsingCollector = utils.falsingCollector(),
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationInteractor = utils.authenticationInteractor()
)
startable.start()
@@ -478,6 +485,32 @@
verify(telecomManager).showInCallScreen(any())
}
+ @Test
+ fun showBouncer_whenLockedSimIntroduced() =
+ testScope.runTest {
+ setAuthMethod(AuthenticationMethodModel.None)
+ introduceLockedSim()
+ assertCurrentScene(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun goesToGone_whenSimUnlocked_whileDeviceUnlocked() =
+ testScope.runTest {
+ introduceLockedSim()
+ emulateUiSceneTransition(expectedVisible = true)
+ enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None)
+ assertCurrentScene(SceneKey.Gone)
+ }
+
+ @Test
+ fun showLockscreen_whenSimUnlocked_whileDeviceLocked() =
+ testScope.runTest {
+ introduceLockedSim()
+ emulateUiSceneTransition(expectedVisible = true)
+ enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin)
+ assertCurrentScene(SceneKey.Lockscreen)
+ }
+
/**
* Asserts that the current scene in the view-model matches what's expected.
*
@@ -678,6 +711,35 @@
runCurrent()
}
+ /**
+ * Enters the correct PIN in the sim bouncer UI.
+ *
+ * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+ * before proceeding.
+ *
+ * Does not assert that the device is locked or unlocked.
+ */
+ private fun TestScope.enterSimPin(
+ authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None
+ ) {
+ assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+ .that(getCurrentSceneInUi())
+ .isEqualTo(SceneKey.Bouncer)
+ val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+ assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+ .that(authMethodViewModel)
+ .isInstanceOf(PinBouncerViewModel::class.java)
+
+ val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+ FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+ pinBouncerViewModel.onPinButtonClicked(digit)
+ }
+ pinBouncerViewModel.onAuthenticateButtonClicked()
+ setAuthMethod(authMethodAfterSimUnlock)
+ utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ runCurrent()
+ }
+
/** Changes device wakefulness state from asleep to awake, going through intermediary states. */
private fun TestScope.wakeUpDevice() {
val wakefulnessModel = powerInteractor.detailedWakefulness.value
@@ -718,4 +780,10 @@
runCurrent()
}
}
+
+ private fun TestScope.introduceLockedSim() {
+ setAuthMethod(AuthenticationMethodModel.Sim)
+ utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ runCurrent()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 3f032a4..7f4bbbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -341,6 +341,7 @@
@Test
fun userInput() =
testScope.runTest {
+ assertThat(utils.powerRepository.userTouchRegistered).isFalse()
underTest.onUserInput()
assertThat(utils.powerRepository.userTouchRegistered).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2f654e2..c4ec56c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -89,6 +89,8 @@
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
+ simBouncerInteractor = utils.simBouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
)
@Before
@@ -587,6 +589,64 @@
verify(falsingCollector, times(2)).onBouncerHidden()
}
+ @Test
+ fun switchesToBouncer_whenSimBecomesLocked() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+
+ utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ runCurrent()
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun switchesToLockscreen_whenSimBecomesUnlocked() =
+ testScope.runTest {
+ utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+ prepareState(
+ initialSceneKey = SceneKey.Bouncer,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ )
+ underTest.start()
+ runCurrent()
+ utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ runCurrent()
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
+ testScope.runTest {
+ utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+ prepareState(
+ initialSceneKey = SceneKey.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.None,
+ isDeviceUnlocked = true,
+ isLockscreenEnabled = false,
+ )
+ underTest.start()
+ runCurrent()
+ utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ runCurrent()
+
+ assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
private fun TestScope.prepareState(
isDeviceUnlocked: Boolean = false,
isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 4c8b562..5969bd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.ResourceBooleanFlag
import com.android.systemui.flags.UnreleasedFlag
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -68,6 +69,7 @@
listOf(
AconfigFlags.FLAG_SCENE_CONTAINER,
AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
+ KeyguardShadeMigrationNssl.FLAG_NAME,
)
.forEach { flagToken ->
setFlagsRule.enableFlags(flagToken)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
index 152be65..6e487cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt
@@ -25,9 +25,9 @@
import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -182,5 +182,15 @@
brightnessControllerFactory,
mainExecutor,
accessibilityMgr
- )
+ ) {
+ private var finishing = false
+
+ override fun isFinishing(): Boolean {
+ return finishing
+ }
+
+ override fun requestFinish() {
+ finishing = true
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f4c05e0..ba8a666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -100,6 +100,8 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
@@ -122,11 +124,12 @@
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
import com.android.systemui.scene.SceneTestUtils;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -333,6 +336,7 @@
@Mock private CastController mCastController;
@Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
@Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
+ @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@@ -371,12 +375,13 @@
MockitoAnnotations.initMocks(this);
mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false);
mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
- mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false);
mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
- mFeatureFlags.set(Flags.MIGRATE_NSSL, false);
mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
- mFeatureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false);
mFeatureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false);
+
+ mSetFlagsRule.disableFlags(KeyguardShadeMigrationNssl.FLAG_NAME);
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+
mMainDispatcher = getMainDispatcher();
KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
KeyguardInteractorFactory.create();
@@ -387,26 +392,27 @@
mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
StateFlowKt.MutableStateFlow(false));
- mShadeInteractor = new ShadeInteractor(
+ mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
new FakeDeviceProvisioningRepository(),
new FakeDisableFlagsRepository(),
mDozeParameters,
- new FakeSceneContainerFlags(),
- mUtils::sceneInteractor,
mFakeKeyguardRepository,
mKeyguardTransitionInteractor,
mPowerInteractor,
new FakeUserSetupRepository(),
mock(UserSwitcherInteractor.class),
- new SharedNotificationContainerInteractor(
- new FakeConfigurationRepository(),
- mContext,
- new ResourcesSplitShadeStateController()
- ),
- mShadeRepository
+ new ShadeInteractorLegacyImpl(
+ mTestScope.getBackgroundScope(),
+ mFakeKeyguardRepository,
+ new SharedNotificationContainerInteractor(
+ new FakeConfigurationRepository(),
+ mContext,
+ new ResourcesSplitShadeStateController()
+ ),
+ mShadeRepository
+ )
);
-
SystemClock systemClock = new FakeSystemClock();
mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor);
@@ -423,7 +429,6 @@
mDozeParameters,
mScreenOffAnimationController,
mKeyguardLogger,
- mFeatureFlags,
mInteractionJankMonitor,
mKeyguardInteractor,
mKeyguardTransitionInteractor,
@@ -497,6 +502,7 @@
when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
when(mQs.getView()).thenReturn(mView);
when(mQSFragment.getView()).thenReturn(mView);
+ when(mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()).thenReturn(true);
doAnswer(invocation -> {
mFragmentListener = invocation.getArgument(1);
return null;
@@ -707,7 +713,8 @@
mKeyguardFaceAuthInteractor,
new ResourcesSplitShadeStateController(),
mPowerInteractor,
- mKeyguardClockPositionAlgorithm);
+ mKeyguardClockPositionAlgorithm,
+ mNaturalScrollingSettingObserver);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
@@ -770,7 +777,6 @@
mAccessibilityManager,
mLockscreenGestureLogger,
mMetricsLogger,
- mFeatureFlags,
mInteractionJankMonitor,
mShadeLog,
mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index d573764..722fb2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -59,6 +59,7 @@
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.systemui.DejankUtils;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
@@ -1105,7 +1106,7 @@
@Test
public void nsslFlagEnabled_allowOnlyExternalTouches() {
- mFeatureFlags.set(Flags.MIGRATE_NSSL, true);
+ mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 7ad84d6..4df7ef5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.shade
-import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.HapticFeedbackConstants
@@ -26,13 +25,10 @@
import com.android.internal.util.CollectionUtils
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -62,9 +58,6 @@
override fun getMainDispatcher() = Dispatchers.Main.immediate
- private val ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
- VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false)
-
@Test
fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest {
launch(Dispatchers.Main.immediate) { givenViewAttached() }
@@ -158,31 +151,8 @@
}
@Test
- fun doubleTapRequired_onKeyguard_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+ fun doubleTapRequired_onKeyguard_usesPerformHapticFeedback() = runTest {
launch(Dispatchers.Main.immediate) {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
- val listener = getFalsingTapListener()
- mStatusBarStateController.setState(KEYGUARD)
-
- listener.onAdditionalTapRequired()
- val packageName = mView.context.packageName
- verify(mKeyguardIndicationController).showTransientIndication(anyInt())
- verify(mVibratorHelper)
- .vibrate(
- any(),
- eq(packageName),
- eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
- eq("falsing-additional-tap-required"),
- eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
- )
- }
- advanceUntilIdle()
- }
-
- @Test
- fun doubleTapRequired_onKeyguard_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
- launch(Dispatchers.Main.immediate) {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
val listener = getFalsingTapListener()
mStatusBarStateController.setState(KEYGUARD)
@@ -208,32 +178,8 @@
}
@Test
- fun doubleTapRequired_shadeLocked_oneWayHapticsDisabled_usesOldVibrate() = runTest {
+ fun doubleTapRequired_shadeLocked_usesPerformHapticFeedback() = runTest {
launch(Dispatchers.Main.immediate) {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
- val listener = getFalsingTapListener()
- val packageName = mView.context.packageName
- mStatusBarStateController.setState(SHADE_LOCKED)
-
- listener.onAdditionalTapRequired()
- verify(mVibratorHelper)
- .vibrate(
- any(),
- eq(packageName),
- eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT),
- eq("falsing-additional-tap-required"),
- eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES)
- )
-
- verify(mTapAgainViewController).show()
- }
- advanceUntilIdle()
- }
-
- @Test
- fun doubleTapRequired_shadeLocked_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest {
- launch(Dispatchers.Main.immediate) {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
val listener = getFalsingTapListener()
mStatusBarStateController.setState(SHADE_LOCKED)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index e6cd17f..8403ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -51,7 +51,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
@@ -68,7 +67,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
@@ -80,6 +78,8 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -128,22 +128,22 @@
@Mock private KeyguardViewMediator mKeyguardViewMediator;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private SysuiColorExtractor mColorExtractor;
- @Mock ColorExtractor.GradientColors mGradientColors;
+ @Mock private ColorExtractor.GradientColors mGradientColors;
@Mock private DumpManager mDumpManager;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private AuthController mAuthController;
- @Mock private ShadeExpansionStateManager mShadeExpansionStateManager;
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private UserTracker mUserTracker;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
+
private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
- private SceneTestUtils mUtils = new SceneTestUtils(this);
- private TestScope mTestScope = mUtils.getTestScope();
+ private final SceneTestUtils mUtils = new SceneTestUtils(this);
+ private final TestScope mTestScope = mUtils.getTestScope();
private ShadeInteractor mShadeInteractor;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -167,11 +167,10 @@
FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakePowerRepository powerRepository = new FakePowerRepository();
- PowerInteractor powerInteractor = new PowerInteractor(
- powerRepository,
- new FalsingCollectorFake(),
+ PowerInteractor powerInteractor = mUtils.powerInteractor(
+ mUtils.getPowerRepository(),
+ mUtils.falsingCollector(),
mScreenOffAnimationController,
mStatusBarStateController);
@@ -180,7 +179,7 @@
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
mUtils.fakeSceneContainerConfig()),
- powerRepository,
+ powerInteractor,
mock(SceneLogger.class));
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
@@ -234,25 +233,26 @@
mKeyguardSecurityModel,
mSelectedUserInteractor,
powerInteractor);
-
- mShadeInteractor =
- new ShadeInteractor(
+ mShadeInteractor = new ShadeInteractorImpl(
+ mTestScope.getBackgroundScope(),
+ new FakeDeviceProvisioningRepository(),
+ new FakeDisableFlagsRepository(),
+ mock(DozeParameters.class),
+ keyguardRepository,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ new FakeUserSetupRepository(),
+ mock(UserSwitcherInteractor.class),
+ new ShadeInteractorLegacyImpl(
mTestScope.getBackgroundScope(),
- new FakeDeviceProvisioningRepository(),
- new FakeDisableFlagsRepository(),
- mock(DozeParameters.class),
- sceneContainerFlags,
- () -> sceneInteractor,
keyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mock(UserSwitcherInteractor.class),
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
new ResourcesSplitShadeStateController()),
- shadeRepository);
+ shadeRepository
+ )
+ );
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 5459779..2dd0af7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -53,7 +53,6 @@
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
-import com.android.systemui.flags.Flags.MIGRATE_NSSL
import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
@@ -67,6 +66,7 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.log.BouncerLogger
@@ -198,7 +198,6 @@
featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- featureFlagsClassic.set(MIGRATE_NSSL, false)
featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
mCommunalRepository = FakeCommunalRepository()
@@ -468,7 +467,7 @@
// AND the lock icon wants the touch
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true)
- featureFlagsClassic.set(MIGRATE_NSSL, true)
+ mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -487,7 +486,7 @@
whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
.thenReturn(false)
- featureFlagsClassic.set(MIGRATE_NSSL, true)
+ mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -506,7 +505,7 @@
whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
.thenReturn(true)
- featureFlagsClassic.set(MIGRATE_NSSL, true)
+ mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index a6ab6a5..4b62906 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -192,7 +192,6 @@
featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- featureFlags.set(Flags.MIGRATE_NSSL, false)
featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
testScope = TestScope()
controller =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 778cfa6..88a47eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -65,7 +65,10 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
+/**
+ * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests
+ * will be deleted.
+ */
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -101,11 +104,7 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.MIGRATE_NSSL, false)
- set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false)
- }
+ featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 2342003..1f37ca0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.flags.Flags
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.fragments.FragmentService
+import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
import com.android.systemui.plugins.qs.QS
@@ -100,11 +101,8 @@
MockitoAnnotations.initMocks(this)
fakeSystemClock = FakeSystemClock()
delayableExecutor = FakeExecutor(fakeSystemClock)
- featureFlags =
- FakeFeatureFlags().apply {
- set(Flags.MIGRATE_NSSL, true)
- set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true)
- }
+ mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME)
+ featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) }
mContext.ensureTestableResources()
whenever(view.context).thenReturn(mContext)
whenever(view.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 6d04887..26b84e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -37,7 +37,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -58,7 +57,6 @@
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
@@ -70,6 +68,8 @@
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -169,6 +169,7 @@
@Mock protected CastController mCastController;
@Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
@Mock protected SelectedUserInteractor mSelectedUserInteractor;
+
protected FakeDisableFlagsRepository mDisableFlagsRepository =
new FakeDisableFlagsRepository();
protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
@@ -198,12 +199,11 @@
new FakeDeviceProvisioningRepository();
deviceProvisioningRepository.setDeviceProvisioned(true);
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
- FakePowerRepository powerRepository = new FakePowerRepository();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- PowerInteractor powerInteractor = new PowerInteractor(
- powerRepository,
- new FalsingCollectorFake(),
+ PowerInteractor powerInteractor = mUtils.powerInteractor(
+ mUtils.getPowerRepository(),
+ mUtils.falsingCollector(),
mock(ScreenOffAnimationController.class),
mStatusBarStateController);
@@ -212,7 +212,7 @@
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
mUtils.fakeSceneContainerConfig()),
- powerRepository,
+ powerInteractor,
mock(SceneLogger.class));
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
@@ -251,7 +251,7 @@
new InWindowLauncherUnlockAnimationRepository(),
mTestScope.getBackgroundScope(),
keyguardTransitionInteractor,
- () -> new FakeKeyguardSurfaceBehindRepository(),
+ FakeKeyguardSurfaceBehindRepository::new,
mock(ActivityManagerWrapper.class)
)
);
@@ -269,25 +269,26 @@
ResourcesSplitShadeStateController splitShadeStateController =
new ResourcesSplitShadeStateController();
- mShadeInteractor =
- new ShadeInteractor(
+ mShadeInteractor = new ShadeInteractorImpl(
+ mTestScope.getBackgroundScope(),
+ deviceProvisioningRepository,
+ mDisableFlagsRepository,
+ mDozeParameters,
+ mKeyguardRepository,
+ keyguardTransitionInteractor,
+ powerInteractor,
+ new FakeUserSetupRepository(),
+ mUserSwitcherInteractor,
+ new ShadeInteractorLegacyImpl(
mTestScope.getBackgroundScope(),
- deviceProvisioningRepository,
- mDisableFlagsRepository,
- mDozeParameters,
- sceneContainerFlags,
- () -> sceneInteractor,
mKeyguardRepository,
- keyguardTransitionInteractor,
- powerInteractor,
- new FakeUserSetupRepository(),
- mUserSwitcherInteractor,
new SharedNotificationContainerInteractor(
configurationRepository,
mContext,
splitShadeStateController),
mShadeRepository
- );
+ )
+ );
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -355,7 +356,6 @@
mAccessibilityManager,
mLockscreenGestureLogger,
mMetricsLogger,
- mFeatureFlags,
mInteractionJankMonitor,
mShadeLogger,
mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index 7cb6d93..997e0e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -285,6 +285,20 @@
}
@Test
+ public void updateQsState_fullscreenTrue() {
+ mQsController.setExpanded(true);
+ mQsController.updateQsState();
+ assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isTrue();
+ }
+
+ @Test
+ public void updateQsState_fullscreenFalse() {
+ mQsController.setExpanded(false);
+ mQsController.updateQsState();
+ assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isFalse();
+ }
+
+ @Test
public void shadeExpanded_onKeyguard() {
mStatusBarStateController.setState(KEYGUARD);
// set maxQsExpansion in NPVC
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index 20b19fd..5f8777d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -207,4 +207,22 @@
underTest.setLegacyIsQsExpanded(true)
assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true)
}
+
+ @Test
+ fun updateLegacyExpandImmediate() =
+ testScope.runTest {
+ assertThat(underTest.legacyExpandImmediate.value).isEqualTo(false)
+
+ underTest.setLegacyExpandImmediate(true)
+ assertThat(underTest.legacyExpandImmediate.value).isEqualTo(true)
+ }
+
+ @Test
+ fun updateLegacyQsFullscreen() =
+ testScope.runTest {
+ assertThat(underTest.legacyQsFullscreen.value).isEqualTo(false)
+
+ underTest.setLegacyQsFullscreen(true)
+ assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index ff7443f..09700e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.shade.data.repository
+package com.android.systemui.shade.domain.interactor
import android.app.StatusBarManager.DISABLE2_NONE
import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE
@@ -46,9 +46,7 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.ObservableTransitionState
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.phone.DozeParameters
@@ -62,14 +60,12 @@
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
@SmallTest
-class ShadeInteractorTest : SysuiTestCase() {
+class ShadeInteractorImplTest : SysuiTestCase() {
@SysUISingleton
@Component(
@@ -79,7 +75,7 @@
UserDomainLayerModule::class,
]
)
- interface TestComponent : SysUITestComponent<ShadeInteractor> {
+ interface TestComponent : SysUITestComponent<ShadeInteractorImpl> {
val configurationRepository: FakeConfigurationRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
@@ -105,7 +101,7 @@
private val dozeParameters: DozeParameters = mock()
private val testComponent: TestComponent =
- DaggerShadeInteractorTest_TestComponent.factory()
+ DaggerShadeInteractorImplTest_TestComponent.factory()
.create(
test = this,
featureFlags =
@@ -446,154 +442,6 @@
}
@Test
- fun lockscreenShadeExpansion_idle_onScene() =
- testComponent.runTest() {
- // GIVEN an expansion flow based on transitions to and from a scene
- val key = SceneKey.Shade
- val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
- val expansionAmount by collectLastValue(expansion)
-
- // WHEN transition state is idle on the scene
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN expansion is 1
- assertThat(expansionAmount).isEqualTo(1f)
- }
-
- @Test
- fun lockscreenShadeExpansion_idle_onDifferentScene() =
- testComponent.runTest() {
- // GIVEN an expansion flow based on transitions to and from a scene
- val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
- val expansionAmount by collectLastValue(expansion)
-
- // WHEN transition state is idle on a different scene
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Idle(SceneKey.Lockscreen)
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN expansion is 0
- assertThat(expansionAmount).isEqualTo(0f)
- }
-
- @Test
- fun lockscreenShadeExpansion_transitioning_toScene() =
- testComponent.runTest() {
- // GIVEN an expansion flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
- val expansionAmount by collectLastValue(expansion)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = key,
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN expansion is 0
- assertThat(expansionAmount).isEqualTo(0f)
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN expansion matches the progress
- assertThat(expansionAmount).isEqualTo(.4f)
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN expansion is 1
- assertThat(expansionAmount).isEqualTo(1f)
- }
-
- @Test
- fun lockscreenShadeExpansion_transitioning_fromScene() =
- testComponent.runTest() {
- // GIVEN an expansion flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
- val expansionAmount by collectLastValue(expansion)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = key,
- toScene = SceneKey.Lockscreen,
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN expansion is 1
- assertThat(expansionAmount).isEqualTo(1f)
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN expansion reflects the progress
- assertThat(expansionAmount).isEqualTo(.6f)
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN expansion is 0
- assertThat(expansionAmount).isEqualTo(0f)
- }
-
- @Test
- fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
- testComponent.runTest() {
- // GIVEN an expansion flow based on transitions to and from a scene
- val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
- val expansionAmount by collectLastValue(expansion)
-
- // WHEN transition state is starting to between different scenes
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = SceneKey.Shade,
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN expansion is 0
- assertThat(expansionAmount).isEqualTo(0f)
-
- // WHEN transition state is partially complete
- progress.value = .4f
-
- // THEN expansion is still 0
- assertThat(expansionAmount).isEqualTo(0f)
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN expansion is still 0
- assertThat(expansionAmount).isEqualTo(0f)
- }
-
- @Test
fun userInteractingWithShade_shadeDraggedUpAndDown() =
testComponent.runTest() {
val actual by collectLastValue(underTest.isUserInteractingWithShade)
@@ -815,199 +663,6 @@
// THEN user is not interacting
assertThat(actual).isFalse()
}
- @Test
- fun userInteracting_idle() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val key = SceneKey.Shade
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is idle
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
- }
-
- @Test
- fun userInteracting_transitioning_toScene_programmatic() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = key,
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
- }
-
- @Test
- fun userInteracting_transitioning_toScene_userInputDriven() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = key,
- progress = progress,
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
- }
-
- @Test
- fun userInteracting_transitioning_fromScene_programmatic() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = key,
- toScene = SceneKey.Lockscreen,
- progress = progress,
- isInitiatedByUserInput = false,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
- }
-
- @Test
- fun userInteracting_transitioning_fromScene_userInputDriven() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val key = SceneKey.QuickSettings
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is starting to move to the scene
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = key,
- toScene = SceneKey.Lockscreen,
- progress = progress,
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
-
- // WHEN transition state is partially to the scene
- progress.value = .4f
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
-
- // WHEN transition completes
- progress.value = 1f
-
- // THEN interacting is true
- assertThat(interacting).isTrue()
- }
-
- @Test
- fun userInteracting_transitioning_toAndFromDifferentScenes() =
- testComponent.runTest() {
- // GIVEN an interacting flow based on transitions to and from a scene
- val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
- val interacting by collectLastValue(interactingFlow)
-
- // WHEN transition state is starting to between different scenes
- val progress = MutableStateFlow(0f)
- val transitionState =
- MutableStateFlow<ObservableTransitionState>(
- ObservableTransitionState.Transition(
- fromScene = SceneKey.Lockscreen,
- toScene = SceneKey.QuickSettings,
- progress = MutableStateFlow(0f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(false),
- )
- )
- sceneInteractor.setTransitionState(transitionState)
-
- // THEN interacting is false
- assertThat(interacting).isFalse()
- }
@Test
fun isShadeTouchable_isFalse_whenFrpIsActive() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
new file mode 100644
index 0000000..f3c875e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -0,0 +1,415 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class ShadeInteractorLegacyImplTest : SysuiTestCase() {
+
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<ShadeInteractorLegacyImpl> {
+
+ val configurationRepository: FakeConfigurationRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ val powerRepository: FakePowerRepository
+ val sceneInteractor: SceneInteractor
+ val shadeRepository: FakeShadeRepository
+ val userRepository: FakeUserRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+ }
+
+ private val dozeParameters: DozeParameters = mock()
+
+ private val testComponent: TestComponent =
+ DaggerShadeInteractorLegacyImplTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks =
+ TestMocksModule(
+ dozeParameters = dozeParameters,
+ ),
+ )
+
+ @Before
+ fun setUp() {
+ runBlocking {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or
+ UserInfo.FLAG_ADMIN or
+ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ )
+ testComponent.apply {
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ }
+ }
+ }
+
+ @Test
+ fun fullShadeExpansionWhenShadeLocked() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
+ shadeRepository.setLockscreenShadeExpansion(0.5f)
+
+ assertThat(actual).isEqualTo(1f)
+ }
+
+ @Test
+ fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+
+ shadeRepository.setLockscreenShadeExpansion(0.5f)
+ assertThat(actual).isEqualTo(0.5f)
+
+ shadeRepository.setLockscreenShadeExpansion(0.8f)
+ assertThat(actual).isEqualTo(0.8f)
+ }
+
+ @Test
+ fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ // WHEN split shade is enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationRepository.onAnyConfigurationChange()
+ shadeRepository.setQsExpansion(.5f)
+ shadeRepository.setLegacyShadeExpansion(.7f)
+ runCurrent()
+
+ // THEN legacy shade expansion is passed through
+ assertThat(actual).isEqualTo(.7f)
+ }
+
+ @Test
+ fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ // WHEN split shade is not enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ shadeRepository.setQsExpansion(.5f)
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN shade expansion is zero
+ assertThat(actual).isEqualTo(0f)
+ }
+
+ @Test
+ fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.shadeExpansion)
+
+ // WHEN split shade is not enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLegacyShadeExpansion(.6f)
+
+ // THEN shade expansion is zero
+ assertThat(actual).isEqualTo(.6f)
+ }
+
+ @Test
+ fun userInteractingWithShade_shadeDraggedUpAndDown() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged down halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully expanded but tracking is not stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully collapsed but tracking is not stopped
+ shadeRepository.setLegacyShadeExpansion(0f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged halfway and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(.6f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade completes expansion stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadeExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged down halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully expanded and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadePartiallyExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade partially expanded
+ shadeRepository.setLegacyShadeExpansion(.4f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN tracking is stopped
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade goes back to collapsed
+ shadeRepository.setLegacyShadeExpansion(0f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadeCollapsed() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade expanded and not tracking input
+ shadeRepository.setLegacyShadeExpansion(1f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged up halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully collapsed and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithQs_qsDraggedUpAndDown() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithQs)
+ // GIVEN qs collapsed and not tracking input
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLegacyQsTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN qs tracking starts
+ shadeRepository.setLegacyQsTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs dragged down halfway
+ shadeRepository.setQsExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs fully expanded but tracking is not stopped
+ shadeRepository.setQsExpansion(1f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs fully collapsed but tracking is not stopped
+ shadeRepository.setQsExpansion(0f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs dragged halfway and tracking is stopped
+ shadeRepository.setQsExpansion(.6f)
+ shadeRepository.setLegacyQsTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs completes expansion stopped
+ shadeRepository.setQsExpansion(1f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
new file mode 100644
index 0000000..470e0c6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -0,0 +1,583 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.collectLastValue
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FakeFeatureFlagsClassicModule
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth
+import dagger.BindsInstance
+import dagger.Component
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+
+@SmallTest
+class ShadeInteractorSceneContainerImplTest : SysuiTestCase() {
+
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<ShadeInteractorSceneContainerImpl> {
+
+ val configurationRepository: FakeConfigurationRepository
+ val keyguardRepository: FakeKeyguardRepository
+ val keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ val powerRepository: FakePowerRepository
+ val sceneInteractor: SceneInteractor
+ val shadeRepository: FakeShadeRepository
+ val userRepository: FakeUserRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance test: SysuiTestCase,
+ featureFlags: FakeFeatureFlagsClassicModule,
+ mocks: TestMocksModule,
+ ): TestComponent
+ }
+ }
+
+ private val dozeParameters: DozeParameters = mock()
+
+ private val testComponent: TestComponent =
+ DaggerShadeInteractorSceneContainerImplTest_TestComponent.factory()
+ .create(
+ test = this,
+ featureFlags =
+ FakeFeatureFlagsClassicModule {
+ set(Flags.FACE_AUTH_REFACTOR, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ },
+ mocks =
+ TestMocksModule(
+ dozeParameters = dozeParameters,
+ ),
+ )
+
+ @Before
+ fun setUp() {
+ runBlocking {
+ val userInfos =
+ listOf(
+ UserInfo(
+ /* id= */ 0,
+ /* name= */ "zero",
+ /* iconPath= */ "",
+ /* flags= */ UserInfo.FLAG_PRIMARY or
+ UserInfo.FLAG_ADMIN or
+ UserInfo.FLAG_FULL,
+ UserManager.USER_TYPE_FULL_SYSTEM,
+ ),
+ )
+ testComponent.apply {
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ }
+ }
+ }
+
+ @Ignore("b/309825977")
+ @Test
+ fun qsExpansionWhenInSplitShadeAndQsExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.qsExpansion)
+
+ // WHEN split shade is enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ configurationRepository.onAnyConfigurationChange()
+ val progress = MutableStateFlow(.3f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.QuickSettings,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN legacy shade expansion is passed through
+ Truth.assertThat(actual).isEqualTo(.3f)
+ }
+
+ @Ignore("b/309825977")
+ @Test
+ fun qsExpansionWhenNotInSplitShadeAndQsExpanded() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.qsExpansion)
+
+ // WHEN split shade is not enabled and QS is expanded
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ val progress = MutableStateFlow(.3f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.QuickSettings,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN shade expansion is zero
+ Truth.assertThat(actual).isEqualTo(.7f)
+ }
+
+ @Test
+ fun qsFullscreen_falseWhenTransitioning() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isQsFullscreen)
+
+ // WHEN scene transition active
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ val progress = MutableStateFlow(.3f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.QuickSettings,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN QS is not fullscreen
+ Truth.assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun qsFullscreen_falseWhenIdleNotQS() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isQsFullscreen)
+
+ // WHEN Idle but not on QuickSettings scene
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Shade)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN QS is not fullscreen
+ Truth.assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun qsFullscreen_trueWhenIdleQS() =
+ testComponent.runTest() {
+ val actual by collectLastValue(underTest.isQsFullscreen)
+
+ // WHEN Idle on QuickSettings scene
+ keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.QuickSettings)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ runCurrent()
+
+ // THEN QS is fullscreen
+ Truth.assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_idle_onScene() =
+ testComponent.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.Shade
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is idle on the scene
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 1
+ Truth.assertThat(expansionAmount).isEqualTo(1f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_idle_onDifferentScene() =
+ testComponent.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is idle on a different scene
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_toScene() =
+ testComponent.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = key,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN expansion matches the progress
+ Truth.assertThat(expansionAmount).isEqualTo(.4f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is 1
+ Truth.assertThat(expansionAmount).isEqualTo(1f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_fromScene() =
+ testComponent.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = key,
+ toScene = SceneKey.Lockscreen,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 1
+ Truth.assertThat(expansionAmount).isEqualTo(1f)
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN expansion reflects the progress
+ Truth.assertThat(expansionAmount).isEqualTo(.6f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
+ testComponent.runTest() {
+ // GIVEN an expansion flow based on transitions to and from a scene
+ val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+ val expansionAmount by collectLastValue(expansion)
+
+ // WHEN transition state is starting to between different scenes
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.Shade,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN expansion is 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition state is partially complete
+ progress.value = .4f
+
+ // THEN expansion is still 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN expansion is still 0
+ Truth.assertThat(expansionAmount).isEqualTo(0f)
+ }
+
+ @Test
+ fun userInteracting_idle() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val key = SceneKey.Shade
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is idle
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+ }
+
+ @Test
+ fun userInteracting_transitioning_toScene_programmatic() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = key,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+ }
+
+ @Test
+ fun userInteracting_transitioning_toScene_userInputDriven() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = key,
+ progress = progress,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+ }
+
+ @Test
+ fun userInteracting_transitioning_fromScene_programmatic() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = key,
+ toScene = SceneKey.Lockscreen,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+ }
+
+ @Test
+ fun userInteracting_transitioning_fromScene_userInputDriven() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val key = SceneKey.QuickSettings
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is starting to move to the scene
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = key,
+ toScene = SceneKey.Lockscreen,
+ progress = progress,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+
+ // WHEN transition state is partially to the scene
+ progress.value = .4f
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+
+ // WHEN transition completes
+ progress.value = 1f
+
+ // THEN interacting is true
+ Truth.assertThat(interacting).isTrue()
+ }
+
+ @Test
+ fun userInteracting_transitioning_toAndFromDifferentScenes() =
+ testComponent.runTest() {
+ // GIVEN an interacting flow based on transitions to and from a scene
+ val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade)
+ val interacting by collectLastValue(interactingFlow)
+
+ // WHEN transition state is starting to between different scenes
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Transition(
+ fromScene = SceneKey.Lockscreen,
+ toScene = SceneKey.QuickSettings,
+ progress = MutableStateFlow(0f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ // THEN interacting is false
+ Truth.assertThat(interacting).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index fa849fe..0d3b2b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -76,6 +77,8 @@
scope = testScope.backgroundScope,
)
+ private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() }
+
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var underTest: ShadeSceneViewModel
@@ -97,6 +100,7 @@
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
+ qsSceneAdapter = qsFlexiglassAdapter,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
index d925d0a..ea7c068 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
@@ -23,6 +23,7 @@
import com.android.systemui.ExpandHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.mockito.mock
@@ -48,16 +49,19 @@
private val dragDownloadCallback: LockscreenShadeTransitionController = mock()
private val expandableView: ExpandableView = mock()
private val expandCallback: ExpandHelper.Callback = mock()
+ private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver = mock()
@Before
fun setUp() {
whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+ whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true)
dragDownHelper = DragDownHelper(
falsingManager,
falsingCollector,
dragDownloadCallback,
- mContext
+ naturalScrollingSettingObserver,
+ mContext,
).also {
it.expandCallback = expandCallback
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 970a0f7..43922e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -14,6 +14,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -99,6 +100,7 @@
@Mock lateinit var stackscroller: NotificationStackScrollLayout
@Mock lateinit var statusbarStateController: SysuiStatusBarStateController
@Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+ @Mock lateinit var naturalScrollingSettingObserver: NaturalScrollingSettingObserver
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -123,6 +125,7 @@
whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true)
whenever(lockScreenUserManager.isLockscreenPublicMode(anyInt())).thenReturn(true)
whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
+ whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true)
testComponent =
DaggerLockscreenShadeTransitionControllerTest_TestComponent.factory()
@@ -185,6 +188,7 @@
shadeInteractor = testComponent.shadeInteractor,
powerInteractor = testComponent.powerInteractor,
splitShadeStateController = ResourcesSplitShadeStateController(),
+ naturalScrollingSettingObserver = naturalScrollingSettingObserver,
)
transitionController.addCallback(transitionControllerCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 4b79a49..8fa7cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
@@ -153,23 +155,25 @@
mock(),
mock(),
powerInteractor)
- shadeInteractor = ShadeInteractor(
+ shadeInteractor = ShadeInteractorImpl(
testScope.backgroundScope,
FakeDeviceProvisioningRepository(),
FakeDisableFlagsRepository(),
mock(),
- sceneContainerFlags,
- utils::sceneInteractor,
keyguardRepository,
keyguardTransitionInteractor,
powerInteractor,
FakeUserSetupRepository(),
mock(),
- SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- ResourcesSplitShadeStateController()),
- shadeRepository,
+ ShadeInteractorLegacyImpl(
+ testScope.backgroundScope,
+ keyguardRepository,
+ SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ ResourcesSplitShadeStateController()),
+ shadeRepository,
+ )
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 1c62161..3e331a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -76,6 +76,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -126,6 +127,7 @@
DeviceProvisionedController mDeviceProvisionedController;
FakeSystemClock mSystemClock;
FakeGlobalSettings mGlobalSettings;
+ FakeEventLog mEventLog;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@@ -138,6 +140,7 @@
mSystemClock = new FakeSystemClock();
mGlobalSettings = new FakeGlobalSettings();
mGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON);
+ mEventLog = new FakeEventLog();
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
@@ -155,7 +158,8 @@
mUserTracker,
mDeviceProvisionedController,
mSystemClock,
- mGlobalSettings);
+ mGlobalSettings,
+ mEventLog);
mNotifInterruptionStateProvider.mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index e1581ea..acc5cea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -53,6 +53,7 @@
deviceProvisionedController,
systemClock,
globalSettings,
+ eventLog
)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 1064475..9e7df5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -33,6 +33,7 @@
ambientDisplayConfiguration,
batteryController,
deviceProvisionedController,
+ eventLog,
globalSettings,
headsUpManager,
keyguardNotificationVisibilityProvider,
@@ -42,6 +43,7 @@
powerManager,
statusBarStateController,
systemClock,
+ uiEventLogger,
userTracker,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 5e81156..5dcb6c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -47,6 +47,7 @@
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import android.provider.Settings.Global.HEADS_UP_ON
+import com.android.internal.logging.UiEventLogger.UiEventEnum
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.LogBuffer
@@ -63,8 +64,13 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.FakeEventLog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeGlobalSettings
@@ -100,6 +106,7 @@
protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context)
protected val batteryController = FakeBatteryController(leakCheck)
protected val deviceProvisionedController = FakeDeviceProvisionedController()
+ protected val eventLog = FakeEventLog()
protected val flags: NotifPipelineFlags = mock()
protected val globalSettings =
FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) }
@@ -147,18 +154,21 @@
fun testShouldPeek() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_settingDisabled() {
ensurePeekState { hunSettingEnabled = false }
assertShouldNotHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_packageSnoozed_withoutFsi() {
ensurePeekState { hunSnoozed = true }
assertShouldNotHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
@@ -167,6 +177,13 @@
forEachPeekableFsiState {
ensurePeekState { hunSnoozed = true }
assertShouldHeadsUp(entry)
+
+ // The old code logs a UiEvent when a HUN snooze is bypassed because the notification
+ // has an FSI, but that doesn't fit into the new code's suppressor-based logic, so we're
+ // not reimplementing it.
+ if (provider !is NotificationInterruptStateProviderWrapper) {
+ assertNoEventsLogged()
+ }
}
}
@@ -174,42 +191,49 @@
fun testShouldNotPeek_alreadyBubbled() {
ensurePeekState { statusBarState = SHADE }
assertShouldNotHeadsUp(buildPeekEntry { isBubble = true })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_isBubble_shadeLocked() {
ensurePeekState { statusBarState = SHADE_LOCKED }
assertShouldHeadsUp(buildPeekEntry { isBubble = true })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_isBubble_keyguard() {
ensurePeekState { statusBarState = KEYGUARD }
assertShouldHeadsUp(buildPeekEntry { isBubble = true })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_dnd() {
ensurePeekState()
assertShouldNotHeadsUp(buildPeekEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_notImportant() {
ensurePeekState()
assertShouldNotHeadsUp(buildPeekEntry { importance = IMPORTANCE_DEFAULT })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_screenOff() {
ensurePeekState { isScreenOn = false }
assertShouldNotHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_dreaming() {
ensurePeekState { isDreaming = true }
assertShouldNotHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
@@ -219,33 +243,56 @@
}
@Test
+ fun testLogsHunOldWhen() {
+ assertNoEventsLogged()
+
+ ensurePeekState()
+ val entry = buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }
+
+ // The old code logs the "old when" UiEvent unconditionally, so don't expect that it hasn't.
+ if (provider !is NotificationInterruptStateProviderWrapper) {
+ provider.makeUnloggedHeadsUpDecision(entry)
+ assertNoEventsLogged()
+ }
+
+ provider.makeAndLogHeadsUpDecision(entry)
+ assertUiEventLogged(HUN_SUPPRESSED_OLD_WHEN, entry.sbn.uid, entry.sbn.packageName)
+ assertNoSystemEventLogged()
+ }
+
+ @Test
fun testShouldPeek_oldWhen_now() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_oldWhen_notOldEnough() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_oldWhen_zeroWhen() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = 0L })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_oldWhen_negativeWhen() {
ensurePeekState()
assertShouldHeadsUp(buildPeekEntry { whenMs = -1L })
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_oldWhen_fullScreenIntent() {
ensurePeekState()
assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) })
+ assertNoEventsLogged()
}
@Test
@@ -257,6 +304,7 @@
isForegroundService = true
}
)
+ assertNoEventsLogged()
}
@Test
@@ -268,18 +316,21 @@
isUserInitiatedJob = true
}
)
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPeek_hiddenOnKeyguard() {
ensurePeekState({ keyguardShouldHideNotification = true })
assertShouldNotHeadsUp(buildPeekEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldPeek_defaultLegacySuppressor() {
ensurePeekState()
withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) }
+ assertNoEventsLogged()
}
@Test
@@ -288,6 +339,7 @@
withLegacySuppressor(alwaysSuppressesInterruptions) {
assertShouldNotHeadsUp(buildPeekEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -296,6 +348,7 @@
withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
assertShouldNotHeadsUp(buildPeekEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -304,24 +357,28 @@
withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
assertShouldNotHeadsUp(buildPeekEntry())
}
+ assertNoEventsLogged()
}
@Test
fun testShouldPulse() {
ensurePulseState()
assertShouldHeadsUp(buildPulseEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPulse_disabled() {
ensurePulseState { pulseOnNotificationsEnabled = false }
assertShouldNotHeadsUp(buildPulseEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPulse_batterySaver() {
ensurePulseState { isAodPowerSave = true }
assertShouldNotHeadsUp(buildPulseEntry())
+ assertNoEventsLogged()
}
@Test
@@ -330,30 +387,35 @@
assertShouldNotHeadsUp(
buildPulseEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT }
)
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPulse_visibilityOverridePrivate() {
ensurePulseState()
assertShouldNotHeadsUp(buildPulseEntry { visibilityOverride = VISIBILITY_PRIVATE })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPulse_importanceLow() {
ensurePulseState()
assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotPulse_hiddenOnKeyguard() {
ensurePulseState({ keyguardShouldHideNotification = true })
assertShouldNotHeadsUp(buildPulseEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldPulse_defaultLegacySuppressor() {
ensurePulseState()
withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) }
+ assertNoEventsLogged()
}
@Test
@@ -362,6 +424,7 @@
withLegacySuppressor(alwaysSuppressesInterruptions) {
assertShouldNotHeadsUp(buildPulseEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -370,6 +433,7 @@
withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
assertShouldHeadsUp(buildPulseEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -378,6 +442,7 @@
withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
assertShouldHeadsUp(buildPulseEntry())
}
+ assertNoEventsLogged()
}
private fun withPeekAndPulseEntry(
@@ -399,6 +464,7 @@
groupAlertBehavior = GROUP_ALERT_SUMMARY
}) {
assertShouldNotHeadsUp(it)
+ assertNoEventsLogged()
}
}
@@ -410,6 +476,7 @@
groupAlertBehavior = GROUP_ALERT_CHILDREN
}) {
assertShouldHeadsUp(it)
+ assertNoEventsLogged()
}
}
@@ -421,24 +488,30 @@
groupAlertBehavior = GROUP_ALERT_SUMMARY
}) {
assertShouldHeadsUp(it)
+ assertNoEventsLogged()
}
}
@Test
fun testShouldNotHeadsUp_justLaunchedFsi() {
- withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { assertShouldNotHeadsUp(it) }
+ withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) {
+ assertShouldNotHeadsUp(it)
+ assertNoEventsLogged()
+ }
}
@Test
fun testShouldBubble_withIntentAndIcon() {
ensureBubbleState()
assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false })
+ assertNoEventsLogged()
}
@Test
fun testShouldBubble_withShortcut() {
ensureBubbleState()
assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true })
+ assertNoEventsLogged()
}
@Test
@@ -451,6 +524,7 @@
groupAlertBehavior = GROUP_ALERT_SUMMARY
}
)
+ assertNoEventsLogged()
}
@Test
@@ -462,24 +536,28 @@
hasBubbleMetadata = false
}
)
+ assertNoEventsLogged()
}
@Test
fun testShouldNotBubble_missingBubbleMetadata() {
ensureBubbleState()
assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false })
+ assertNoEventsLogged()
}
@Test
fun testShouldNotBubble_notAllowedToBubble() {
ensureBubbleState()
assertShouldNotBubble(buildBubbleEntry { canBubble = false })
+ assertNoEventsLogged()
}
@Test
fun testShouldBubble_defaultLegacySuppressor() {
ensureBubbleState()
withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) }
+ assertNoEventsLogged()
}
@Test
@@ -488,6 +566,7 @@
withLegacySuppressor(alwaysSuppressesInterruptions) {
assertShouldNotBubble(buildBubbleEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -496,6 +575,7 @@
withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
assertShouldNotBubble(buildBubbleEntry())
}
+ assertNoEventsLogged()
}
@Test
@@ -504,17 +584,22 @@
withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) {
assertShouldBubble(buildBubbleEntry())
}
+ assertNoEventsLogged()
}
@Test
fun testShouldNotBubble_hiddenOnKeyguard() {
ensureBubbleState({ keyguardShouldHideNotification = true })
assertShouldNotBubble(buildBubbleEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldNotFsi_noFullScreenIntent() {
- forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) }
+ forEachFsiState {
+ assertShouldNotFsi(buildFsiEntry { hasFsi = false })
+ assertNoEventsLogged()
+ }
}
@Test
@@ -526,6 +611,7 @@
isStickyAndNotDemoted = true
}
)
+ assertNoEventsLogged()
}
}
@@ -536,12 +622,16 @@
buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT },
expectWouldInterruptWithoutDnd = true
)
+ assertNoEventsLogged()
}
}
@Test
fun testShouldNotFsi_notImportantEnough() {
- forEachFsiState { assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT }) }
+ forEachFsiState {
+ assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT })
+ assertNoEventsLogged()
+ }
}
@Test
@@ -554,6 +644,7 @@
},
expectWouldInterruptWithoutDnd = false
)
+ assertNoEventsLogged()
}
}
@@ -571,6 +662,27 @@
}
@Test
+ fun testLogsFsiSuppressiveGroupAlertBehavior() {
+ ensureNotInteractiveFsiState()
+ val entry = buildFsiEntry {
+ isGrouped = true
+ isGroupSummary = true
+ groupAlertBehavior = GROUP_ALERT_CHILDREN
+ }
+
+ val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
+ assertNoEventsLogged()
+
+ provider.logFullScreenIntentDecision(decision)
+ assertUiEventLogged(
+ FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+ entry.sbn.uid,
+ entry.sbn.packageName
+ )
+ assertSystemEventLogged("231322873", entry.sbn.uid, "groupAlertBehavior")
+ }
+
+ @Test
fun testShouldFsi_suppressiveGroupAlertBehavior_notGrouped() {
forEachFsiState {
assertShouldFsi(
@@ -580,6 +692,7 @@
groupAlertBehavior = GROUP_ALERT_CHILDREN
}
)
+ assertNoEventsLogged()
}
}
@@ -609,26 +722,52 @@
}
@Test
+ fun testLogsFsiSuppressiveBubbleMetadata() {
+ ensureNotInteractiveFsiState()
+ val entry = buildFsiEntry {
+ hasBubbleMetadata = true
+ bubbleSuppressesNotification = true
+ }
+
+ val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
+ assertNoEventsLogged()
+
+ provider.logFullScreenIntentDecision(decision)
+ assertUiEventLogged(
+ FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
+ entry.sbn.uid,
+ entry.sbn.packageName
+ )
+ assertSystemEventLogged("274759612", entry.sbn.uid, "bubbleMetadata")
+ }
+
+ @Test
fun testShouldNotFsi_packageSuspended() {
- forEachFsiState { assertShouldNotFsi(buildFsiEntry { packageSuspended = true }) }
+ forEachFsiState {
+ assertShouldNotFsi(buildFsiEntry { packageSuspended = true })
+ assertNoEventsLogged()
+ }
}
@Test
fun testShouldFsi_notInteractive() {
ensureNotInteractiveFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldFsi_dreaming() {
ensureDreamingFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldFsi_keyguard() {
ensureKeyguardFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
@@ -636,6 +775,7 @@
forEachPeekableFsiState {
ensurePeekState()
assertShouldNotFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
}
@@ -644,6 +784,7 @@
forEachPeekableFsiState {
ensurePeekState { hunSnoozed = true }
assertShouldNotFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
}
@@ -651,18 +792,21 @@
fun testShouldFsi_lockedShade() {
ensureLockedShadeFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldFsi_keyguardOccluded() {
ensureKeyguardOccludedFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
fun testShouldFsi_deviceNotProvisioned() {
ensureDeviceNotProvisionedFsiState()
assertShouldFsi(buildFsiEntry())
+ assertNoEventsLogged()
}
@Test
@@ -672,9 +816,23 @@
}
@Test
+ fun testLogsFsiNoHunOrKeyguard() {
+ ensureNoHunOrKeyguardFsiState()
+ val entry = buildFsiEntry()
+
+ val decision = provider.makeUnloggedFullScreenIntentDecision(entry)
+ assertNoEventsLogged()
+
+ provider.logFullScreenIntentDecision(decision)
+ assertUiEventLogged(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, entry.sbn.uid, entry.sbn.packageName)
+ assertSystemEventLogged("231322873", entry.sbn.uid, "no hun or keyguard")
+ }
+
+ @Test
fun testShouldFsi_defaultLegacySuppressor() {
forEachFsiState {
withLegacySuppressor(neverSuppresses) { assertShouldFsi(buildFsiEntry()) }
+ assertNoEventsLogged()
}
}
@@ -682,6 +840,7 @@
fun testShouldFsi_suppressInterruptions() {
forEachFsiState {
withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) }
+ assertNoEventsLogged()
}
}
@@ -691,6 +850,7 @@
withLegacySuppressor(alwaysSuppressesAwakeInterruptions) {
assertShouldFsi(buildFsiEntry())
}
+ assertNoEventsLogged()
}
}
@@ -698,6 +858,7 @@
fun testShouldFsi_suppressAwakeHeadsUp() {
forEachFsiState {
withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) }
+ assertNoEventsLogged()
}
}
@@ -1080,6 +1241,45 @@
run(block)
}
+ private fun assertNoEventsLogged() {
+ assertNoUiEventLogged()
+ assertNoSystemEventLogged()
+ }
+
+ private fun assertNoUiEventLogged() {
+ assertEquals(0, uiEventLogger.numLogs())
+ }
+
+ private fun assertUiEventLogged(uiEventId: UiEventEnum, uid: Int, packageName: String) {
+ assertEquals(1, uiEventLogger.numLogs())
+
+ val event = uiEventLogger.get(0)
+ assertEquals(uiEventId.id, event.eventId)
+ assertEquals(uid, event.uid)
+ assertEquals(packageName, event.packageName)
+ }
+
+ private fun assertNoSystemEventLogged() {
+ assertEquals(0, eventLog.events.size)
+ }
+
+ private fun assertSystemEventLogged(number: String, uid: Int, description: String) {
+ assertEquals(1, eventLog.events.size)
+
+ val event = eventLog.events[0]
+ assertEquals(0x534e4554, event.tag)
+
+ val value = event.value
+ assertTrue(value is Array<*>)
+
+ if (value is Array<*>) {
+ assertEquals(3, value.size)
+ assertEquals(number, value[0])
+ assertEquals(uid, value[1])
+ assertEquals(description, value[2])
+ }
+ }
+
private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index e7dad6a..912c27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,8 +18,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -30,7 +28,6 @@
import android.app.StatusBarManager;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.os.VibrationEffect;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
import android.view.HapticFeedbackConstants;
@@ -42,7 +39,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
@@ -53,7 +49,6 @@
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -94,14 +89,12 @@
@Mock private DozeServiceHost mDozeServiceHost;
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private PowerManager mPowerManager;
- @Mock private VibratorHelper mVibratorHelper;
@Mock private Vibrator mVibrator;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
@Mock private Lazy<CameraLauncher> mCameraLauncherLazy;
@Mock private UserTracker mUserTracker;
@Mock private QSHost mQSHost;
@Mock private ActivityStarter mActivityStarter;
- private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -131,15 +124,13 @@
mNotificationStackScrollLayoutController,
mStatusBarHideIconsForBouncerManager,
mPowerManager,
- mVibratorHelper,
Optional.of(mVibrator),
new DisableFlagsLogger(),
DEFAULT_DISPLAY,
mCameraLauncherLazy,
mUserTracker,
mQSHost,
- mActivityStarter,
- mFeatureFlags);
+ mActivityStarter);
when(mUserTracker.getUserHandle()).thenReturn(
UserHandle.of(ActivityManager.getCurrentUser()));
@@ -192,18 +183,7 @@
}
@Test
- public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
-
- mSbcqCallbacks.vibrateOnNavigationKeyDown();
-
- verify(mVibratorHelper).vibrate(VibrationEffect.EFFECT_TICK);
- }
-
- @Test
- public void vibrateOnNavigationKeyDown_oneWayHapticsEnabled_usesPerformHapticFeedback() {
- mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-
+ public void vibrateOnNavigationKeyDown_usesPerformHapticFeedback() {
mSbcqCallbacks.vibrateOnNavigationKeyDown();
verify(mShadeViewController).performHapticFeedback(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 86a5c52..251718d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -121,6 +121,8 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.shade.CameraLauncher;
@@ -176,6 +178,8 @@
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.util.EventLog;
+import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
@@ -322,6 +326,7 @@
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
+ private final FakeEventLog mFakeEventLog = new FakeEventLog();
private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock);
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -329,6 +334,8 @@
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
+ private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -370,7 +377,8 @@
mUserTracker,
mDeviceProvisionedController,
mFakeSystemClock,
- mFakeGlobalSettings);
+ mFakeGlobalSettings,
+ mFakeEventLog);
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class));
@@ -555,7 +563,8 @@
mAlternateBouncerInteractor,
mUserTracker,
() -> mFingerprintManager,
- mActivityStarter
+ mActivityStarter,
+ mSceneContainerFlags
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
@@ -1182,7 +1191,8 @@
UserTracker userTracker,
DeviceProvisionedController deviceProvisionedController,
SystemClock systemClock,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ EventLog eventLog) {
super(
powerManager,
ambientDisplayConfiguration,
@@ -1198,7 +1208,8 @@
userTracker,
deviceProvisionedController,
systemClock,
- globalSettings
+ globalSettings,
+ eventLog
);
mUseHeadsUp = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 0a68406..f71114d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -16,7 +16,14 @@
package com.android.systemui.statusbar.phone;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -26,6 +33,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.FakeDisplayTracker;
@@ -81,4 +89,39 @@
verify(mApplier).applyDarkIntensity(eq(0f));
}
+ @Test
+ public void gestureNav_noForceNavButtons_expectNotSupportsIconTint() {
+ GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+ doReturn(false).when(observer).areNavigationButtonForcedVisible();
+ mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+ assertFalse(mLightBarTransitionsController.supportsIconTintForNavMode(
+ NAV_BAR_MODE_GESTURAL));
+ }
+
+ @Test
+ public void gestureNav_forceNavButtons_expectSupportsIconTint() {
+ GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+ doReturn(true).when(observer).areNavigationButtonForcedVisible();
+ mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+ assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+ NAV_BAR_MODE_GESTURAL));
+ }
+
+ @Test
+ public void buttonNav_noForceNavButtons_expectNotSupportsIconTint() {
+ GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+ doReturn(false).when(observer).areNavigationButtonForcedVisible();
+ mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+ assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+ NAV_BAR_MODE_3BUTTON));
+ }
+
+ @Test
+ public void buttonNav_forceNavButtons_expectSupportsIconTint() {
+ GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class);
+ doReturn(true).when(observer).areNavigationButtonForcedVisible();
+ mLightBarTransitionsController.setNavigationSettingsObserver(observer);
+ assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode(
+ NAV_BAR_MODE_3BUTTON));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 1e31977..d1423e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -163,7 +163,6 @@
@Mock
private InteractionJankMonitor mJankMonitor;
private FakePowerRepository mPowerRepository;
- private PowerInteractor mPowerInteractor;
@Mock
private UserTracker mUserTracker;
private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -214,7 +213,7 @@
UserHandle.of(ActivityManager.getCurrentUser()));
mPowerRepository = new FakePowerRepository();
- mPowerInteractor = PowerInteractorFactory.create(
+ PowerInteractor mPowerInteractor = PowerInteractorFactory.create(
mPowerRepository,
new FalsingCollectorFake(),
mScreenOffAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 9aafee4..6a0375d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
@@ -82,14 +80,9 @@
@Mock
private lateinit var handler: Handler
- private lateinit var featureFlags: FakeFeatureFlags
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- featureFlags = FakeFeatureFlags().apply {
- set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)
- }
controller = UnlockedScreenOffAnimationController(
context,
wakefulnessLifecycle,
@@ -102,7 +95,6 @@
interactionJankMonitor,
powerManager,
handler = handler,
- featureFlags,
)
controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
index 3dc7de6..a802381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
@@ -16,12 +16,28 @@
package com.android.systemui.statusbar.pipeline.mobile.util
+import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
/** Fake of [SubscriptionManagerProxy] for easy testing */
class FakeSubscriptionManagerProxy(
/** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */
- var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID
+ var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID,
+ var activeSubscriptionInfo: SubscriptionInfo? = null
) : SubscriptionManagerProxy {
override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId
+
+ override fun isValidSubscriptionId(subId: Int): Boolean {
+ return subId > -1
+ }
+
+ override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? {
+ return activeSubscriptionInfo
+ }
+
+ /** Sets the active subscription info. */
+ fun setActiveSubscriptionInfo(subId: Int, isEmbedded: Boolean = false) {
+ activeSubscriptionInfo =
+ SubscriptionInfo.Builder().setId(subId).setEmbedded(isEmbedded).build()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
index d33806e..1250228 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.policy
-import com.android.systemui.flags.FakeFeatureFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -78,13 +77,11 @@
private lateinit var view: FrameLayout
private lateinit var testableLooper: TestableLooper
private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController
- private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- featureFlags = FakeFeatureFlags()
view = LayoutInflater.from(context)
.inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout
@@ -101,7 +98,6 @@
dozeParameters,
screenOffAnimationController,
userSwitchDialogController,
- featureFlags,
uiEventLogger)
ViewUtils.attachView(view)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt
new file mode 100644
index 0000000..004f679
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.policy.data.repository
+
+import android.app.NotificationManager
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ZenModeController
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ZenModeRepositoryImplTest : SysuiTestCase() {
+ @Mock lateinit var zenModeController: ZenModeController
+
+ lateinit var underTest: ZenModeRepositoryImpl
+
+ private val testPolicy = NotificationManager.Policy(0, 1, 0)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = ZenModeRepositoryImpl(zenModeController)
+ }
+
+ @Test
+ fun zenMode_reflectsCurrentControllerState() = runTest {
+ whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ val zenMode by collectLastValue(underTest.zenMode)
+ assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
+ }
+
+ @Test
+ fun zenMode_updatesWhenControllerStateChanges() = runTest {
+ val zenMode by collectLastValue(underTest.zenMode)
+ runCurrent()
+ whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) }
+ .onZenChanged(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+ }
+
+ @Test
+ fun policy_reflectsCurrentControllerState() {
+ runTest {
+ whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy)
+ val policy by collectLastValue(underTest.consolidatedNotificationPolicy)
+ assertThat(policy).isEqualTo(testPolicy)
+ }
+ }
+
+ @Test
+ fun policy_updatesWhenControllerStateChanges() = runTest {
+ val policy by collectLastValue(underTest.consolidatedNotificationPolicy)
+ runCurrent()
+ whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy)
+ withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) }
+ .onConsolidatedPolicyChanged(testPolicy)
+ assertThat(policy).isEqualTo(testPolicy)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
new file mode 100644
index 0000000..78e7971
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.policy.domain.interactor
+
+import android.app.NotificationManager.Policy
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.SysUITestComponent
+import com.android.SysUITestModule
+import com.android.collectLastValue
+import com.android.runCurrent
+import com.android.runTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository
+import com.android.systemui.user.domain.UserDomainLayerModule
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class ZenModeInteractorTest : SysuiTestCase() {
+ @SysUISingleton
+ @Component(
+ modules =
+ [
+ SysUITestModule::class,
+ UserDomainLayerModule::class,
+ ]
+ )
+ interface TestComponent : SysUITestComponent<ZenModeInteractor> {
+
+ val repository: FakeZenModeRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerZenModeInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun testIsZenModeEnabled_off() =
+ testComponent.runTest {
+ val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+ repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+ runCurrent()
+
+ assertThat(enabled).isFalse()
+ }
+
+ @Test
+ fun testIsZenModeEnabled_alarms() =
+ testComponent.runTest {
+ val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+ repository.zenMode.value = Settings.Global.ZEN_MODE_ALARMS
+ runCurrent()
+
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun testIsZenModeEnabled_importantInterruptions() =
+ testComponent.runTest {
+ val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+ repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun testIsZenModeEnabled_noInterruptions() =
+ testComponent.runTest {
+ val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+ repository.zenMode.value = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun testIsZenModeEnabled_unknown() =
+ testComponent.runTest {
+ val enabled by collectLastValue(underTest.isZenModeEnabled)
+
+ repository.zenMode.value = 4 // this should fail if we ever add another zen mode type
+ runCurrent()
+
+ assertThat(enabled).isFalse()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_noPolicy() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ repository.consolidatedNotificationPolicy.value = null
+ repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(hidden).isFalse()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_zenOffShadeSuppressed() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ repository.consolidatedNotificationPolicy.value =
+ policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ repository.zenMode.value = Settings.Global.ZEN_MODE_OFF
+ runCurrent()
+
+ assertThat(hidden).isFalse()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_zenOnShadeNotSuppressed() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ repository.consolidatedNotificationPolicy.value =
+ policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR)
+ repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(hidden).isFalse()
+ }
+
+ @Test
+ fun testAreNotificationsHiddenInShade_zenOnShadeSuppressed() =
+ testComponent.runTest {
+ val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
+
+ repository.consolidatedNotificationPolicy.value =
+ policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST)
+ repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ runCurrent()
+
+ assertThat(hidden).isTrue()
+ }
+}
+
+fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) =
+ Policy(
+ /* priorityCategories = */ 0,
+ /* priorityCallSenders = */ 0,
+ /* priorityMessageSenders = */ 0,
+ /* suppressedVisualEffects = */ suppressedVisualEffects
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 1e7e184..1466d24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -21,7 +21,6 @@
import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -33,7 +32,6 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.common.shared.model.ContentDescription
@@ -42,9 +40,8 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.TemporaryViewUiEvent
@@ -94,7 +91,6 @@
private lateinit var fakeExecutor: FakeExecutor
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var uiEventLogger: TemporaryViewUiEventLogger
- private val featureFlags = FakeFeatureFlags()
@Before
fun setUp() {
@@ -131,10 +127,8 @@
fakeWakeLockBuilder,
fakeClock,
uiEventLogger,
- featureFlags
)
underTest.start()
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@Test
@@ -494,23 +488,6 @@
)
}
- @Test
- fun displayView_oneWayHapticsEnabled_usesPerformHapticFeedback() {
- featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
- val constant: Int = HapticFeedbackConstants.CONFIRM
- underTest.displayView(
- createChipbarInfo(
- Icon.Resource(R.id.check_box, null),
- Text.Loaded("text"),
- endItem = null,
- vibrationEffect = null,
- vibrationConstant = constant
- )
- )
-
- verify(vibratorHelper).performHapticFeedback(any(), eq(constant))
- }
-
/** Regression test for b/266119467. */
@Test
fun displayView_animationFailure_viewsStillBecomeVisible() {
@@ -729,14 +706,12 @@
endItem: ChipbarEndItem?,
vibrationEffect: VibrationEffect? = null,
allowSwipeToDismiss: Boolean = false,
- vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS,
): ChipbarInfo {
return ChipbarInfo(
TintedIcon(startIcon, tint = null),
text,
endItem,
vibrationEffect,
- vibrationConstant,
allowSwipeToDismiss,
windowTitle = WINDOW_TITLE,
wakeReason = WAKE_REASON,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a580600..aa5f987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -93,7 +93,6 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
-import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
import com.android.systemui.dump.DumpManager;
@@ -112,7 +111,6 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.SceneTestUtils;
@@ -125,10 +123,11 @@
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeWindowLogger;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl;
+import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationEntryHelper;
@@ -163,6 +162,7 @@
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
+import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -249,8 +249,6 @@
private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock
private AuthController mAuthController;
- @Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
@@ -340,8 +338,8 @@
@Mock
private Icon mAppBubbleIcon;
- private SceneTestUtils mUtils = new SceneTestUtils(this);
- private TestScope mTestScope = mUtils.getTestScope();
+ private final SceneTestUtils mUtils = new SceneTestUtils(this);
+ private final TestScope mTestScope = mUtils.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -352,7 +350,7 @@
private TestableLooper mTestableLooper;
- private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private UserHandle mUser0;
@@ -388,12 +386,11 @@
FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository();
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeShadeRepository shadeRepository = new FakeShadeRepository();
- FakePowerRepository powerRepository = new FakePowerRepository();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = new PowerInteractor(
- powerRepository,
- new FalsingCollectorFake(),
+ mUtils.getPowerRepository(),
+ mUtils.falsingCollector(),
mock(ScreenOffAnimationController.class),
mStatusBarStateController);
@@ -402,7 +399,7 @@
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
mUtils.fakeSceneContainerConfig()),
- powerRepository,
+ powerInteractor,
mock(SceneLogger.class));
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
@@ -441,7 +438,7 @@
new InWindowLauncherUnlockAnimationRepository(),
mTestScope.getBackgroundScope(),
keyguardTransitionInteractor,
- () -> new FakeKeyguardSurfaceBehindRepository(),
+ FakeKeyguardSurfaceBehindRepository::new,
mock(ActivityManagerWrapper.class)
)
);
@@ -460,23 +457,24 @@
new ResourcesSplitShadeStateController();
mShadeInteractor =
- new ShadeInteractor(
+ new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
deviceProvisioningRepository,
new FakeDisableFlagsRepository(),
mDozeParameters,
- sceneContainerFlags,
- () -> sceneInteractor,
keyguardRepository,
keyguardTransitionInteractor,
powerInteractor,
new FakeUserSetupRepository(),
mock(UserSwitcherInteractor.class),
- new SharedNotificationContainerInteractor(
- configurationRepository,
- mContext,
- splitShadeStateController),
- new FakeShadeRepository()
+ new ShadeInteractorLegacyImpl(
+ mTestScope.getBackgroundScope(), keyguardRepository,
+ new SharedNotificationContainerInteractor(
+ configurationRepository,
+ mContext,
+ splitShadeStateController),
+ shadeRepository
+ )
);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
@@ -546,7 +544,8 @@
mock(UserTracker.class),
mock(DeviceProvisionedController.class),
mock(SystemClock.class),
- fakeGlobalSettings
+ fakeGlobalSettings,
+ new FakeEventLog()
);
mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index 975555c..c9964c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.EventLog;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
@@ -52,7 +53,8 @@
UserTracker userTracker,
DeviceProvisionedController deviceProvisionedController,
SystemClock systemClock,
- GlobalSettings globalSettings) {
+ GlobalSettings globalSettings,
+ EventLog eventLog) {
super(
powerManager,
ambientDisplayConfiguration,
@@ -68,7 +70,8 @@
userTracker,
deviceProvisionedController,
systemClock,
- globalSettings);
+ globalSettings,
+ eventLog);
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index af1930e..45ded7f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -60,6 +60,8 @@
override val minPatternLength: Int = 4
+ override val minPasswordLength: Int = 4
+
private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false)
override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
_isPinEnhancedPrivacyEnabled.asStateFlow()
@@ -178,6 +180,7 @@
is AuthenticationMethodModel.Password -> SecurityMode.Password
is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
is AuthenticationMethodModel.None -> SecurityMode.None
+ is AuthenticationMethodModel.Sim -> SecurityMode.SimPin
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt
new file mode 100644
index 0000000..890e69d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.systemui.bouncer.data.repository
+
+import android.telephony.SubscriptionInfo
+import com.android.systemui.bouncer.data.model.SimPukInputModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fakes the SimBouncerRepository. */
+class FakeSimBouncerRepository : SimBouncerRepository {
+ private val _subscriptionId: MutableStateFlow<Int> = MutableStateFlow(-1)
+ override val subscriptionId: StateFlow<Int> = _subscriptionId
+ private val _activeSubscriptionInfo: MutableStateFlow<SubscriptionInfo?> =
+ MutableStateFlow(null)
+ override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> = _activeSubscriptionInfo
+ private val _isLockedEsim: MutableStateFlow<Boolean?> = MutableStateFlow(null)
+ override val isLockedEsim: StateFlow<Boolean?> = _isLockedEsim
+ private val _isSimPukLocked: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isSimPukLocked: StateFlow<Boolean> = _isSimPukLocked
+ private val _errorDialogMessage: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val errorDialogMessage: StateFlow<String?> = _errorDialogMessage
+ private var _simPukInputModel = SimPukInputModel()
+ override val simPukInputModel: SimPukInputModel
+ get() = _simPukInputModel
+
+ fun setSubscriptionId(subId: Int) {
+ _subscriptionId.value = subId
+ }
+
+ fun setActiveSubscriptionInfo(subscriptioninfo: SubscriptionInfo) {
+ _activeSubscriptionInfo.value = subscriptioninfo
+ }
+
+ fun setLockedEsim(isLockedEsim: Boolean) {
+ _isLockedEsim.value = isLockedEsim
+ }
+
+ fun setSimPukLocked(isSimPukLocked: Boolean) {
+ _isSimPukLocked.value = isSimPukLocked
+ }
+
+ fun setErrorDialogMessage(msg: String?) {
+ _errorDialogMessage.value = msg
+ }
+
+ override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) {
+ _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin)
+ }
+
+ override fun setSimVerificationErrorMessage(msg: String?) {
+ _errorDialogMessage.value = msg
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 3674244..a94ca29 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -40,7 +40,7 @@
class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository {
private val _transitions =
- MutableSharedFlow<TransitionStep>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ MutableSharedFlow<TransitionStep>(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
init {
@@ -130,7 +130,7 @@
* only a FINISHED step, override [validateStep].
*/
suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
- _transitions.replayCache.getOrNull(0)?.let { lastStep ->
+ _transitions.replayCache.last().let { lastStep ->
if (
validateStep &&
step.transitionState == TransitionState.FINISHED &&
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
new file mode 100644
index 0000000..2902c3f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.qs.ui.adapter
+
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeQSSceneAdapter(
+ private val inflateDelegate: suspend (Context, ViewGroup?) -> View,
+) : QSSceneAdapter {
+ private val _customizing = MutableStateFlow(false)
+ override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow()
+
+ private val _view = MutableStateFlow<View?>(null)
+ override val qsView: Flow<View> = _view.filterNotNull()
+
+ private val _state = MutableStateFlow<QSSceneAdapter.State?>(null)
+ val state = _state.filterNotNull()
+
+ override suspend fun inflate(context: Context, parent: ViewGroup?) {
+ _view.value = inflateDelegate(context, parent)
+ }
+
+ override fun setState(state: QSSceneAdapter.State) {
+ if (_view.value != null) {
+ _state.value = state
+ }
+ }
+
+ fun setCustomizing(value: Boolean) {
+ _customizing.value = value
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 72cc08f..29e73b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -23,6 +23,10 @@
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.telecom.TelecomManager
+import android.telephony.PinResult
+import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
import com.android.internal.logging.MetricsLogger
import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.SysuiTestCase
@@ -32,9 +36,11 @@
import com.android.systemui.bouncer.data.repository.BouncerRepository
import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
@@ -61,7 +67,10 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.domain.interactor.SceneInteractor
@@ -69,6 +78,8 @@
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.data.repository.TelephonyRepository
@@ -85,6 +96,9 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.currentTime
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito
/**
* Utilities for creating scene container framework related repositories, interactors, and
@@ -123,9 +137,33 @@
}
val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
+ val bouncerRepository = BouncerRepository(featureFlags)
val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+ val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() }
+ val telephonyManager: TelephonyManager =
+ Mockito.mock(TelephonyManager::class.java).apply {
+ whenever(createForSubscriptionId(anyInt())).thenReturn(this)
+ whenever(supplyIccLockPin(anyString()))
+ .thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3))
+ }
+ val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy {
+ FakeMobileConnectionsRepository(mock(), mock())
+ }
+
+ val simBouncerInteractor =
+ SimBouncerInteractor(
+ applicationContext = context,
+ backgroundDispatcher = testDispatcher,
+ applicationScope = applicationScope(),
+ repository = simBouncerRepository,
+ telephonyManager = telephonyManager,
+ resources = context.resources,
+ keyguardUpdateMonitor = mock(),
+ euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
+ mobileConnectionsRepository = mobileConnectionsRepository,
+ )
val userRepository: UserRepository by lazy {
FakeUserRepository().apply {
@@ -137,6 +175,7 @@
private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() }
private var falsingInteractor: FalsingInteractor? = null
+ private var powerInteractor: PowerInteractor? = null
fun fakeSceneContainerRepository(
containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
@@ -159,7 +198,7 @@
return SceneInteractor(
applicationScope = applicationScope(),
repository = repository,
- powerRepository = powerRepository,
+ powerInteractor = powerInteractor(),
logger = mock(),
)
}
@@ -223,10 +262,12 @@
return BouncerInteractor(
applicationScope = applicationScope(),
applicationContext = context,
- repository = BouncerRepository(featureFlags),
+ repository = bouncerRepository,
authenticationInteractor = authenticationInteractor,
flags = sceneContainerFlags,
falsingInteractor = falsingInteractor(),
+ powerInteractor = powerInteractor(),
+ simBouncerInteractor = simBouncerInteractor,
)
}
@@ -247,6 +288,7 @@
users = flowOf(users),
userSwitcherMenu = flowOf(createMenuActions()),
actionButtonInteractor = actionButtonInteractor,
+ simBouncerInteractor = simBouncerInteractor,
)
}
@@ -264,6 +306,22 @@
return falsingCollectorFake
}
+ fun powerInteractor(
+ repository: PowerRepository = powerRepository,
+ falsingCollector: FalsingCollector = falsingCollector(),
+ screenOffAnimationController: ScreenOffAnimationController = mock(),
+ statusBarStateController: StatusBarStateController = mock(),
+ ): PowerInteractor {
+ return powerInteractor
+ ?: PowerInteractor(
+ repository = repository,
+ falsingCollector = falsingCollector,
+ screenOffAnimationController = screenOffAnimationController,
+ statusBarStateController = statusBarStateController,
+ )
+ .also { powerInteractor = it }
+ }
+
private fun applicationScope(): CoroutineScope {
return testScope.backgroundScope
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 02318ab..92ec4f2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -66,6 +66,15 @@
_legacyIsQsExpanded.value = legacyIsQsExpanded
}
+ private val _legacyExpandImmediate = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead")
+ override val legacyExpandImmediate = _legacyExpandImmediate
+
+ @Deprecated("Use ShadeInteractor instead")
+ override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) {
+ _legacyExpandImmediate.value = legacyExpandImmediate
+ }
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyExpandedOrAwaitingInputTransfer(
legacyExpandedOrAwaitingInputTransfer: Boolean
@@ -88,6 +97,14 @@
legacyLockscreenShadeTracking.value = tracking
}
+ private val _legacyQsFullscreen = MutableStateFlow(false)
+ @Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen = _legacyQsFullscreen
+
+ @Deprecated("Use ShadeInteractor instead")
+ override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) {
+ _legacyQsFullscreen.value = legacyQsFullscreen
+ }
+
fun setShadeModel(model: ShadeModel) {
_shadeModel.value = model
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
index 5aece1b..16dab40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt
@@ -16,7 +16,14 @@
package com.android.systemui.statusbar.policy.data
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepositoryModule
+import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepositoryModule
import dagger.Module
-@Module(includes = [FakeDeviceProvisioningRepositoryModule::class])
+@Module(
+ includes =
+ [
+ FakeDeviceProvisioningRepositoryModule::class,
+ FakeZenModeRepositoryModule::class,
+ ]
+)
object FakeStatusBarPolicyDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
new file mode 100644
index 0000000..4059930
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.policy.data.repository
+
+import android.app.NotificationManager
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeZenModeRepository @Inject constructor() : ZenModeRepository {
+ override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF)
+ override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> =
+ MutableStateFlow(
+ NotificationManager.Policy(
+ /* priorityCategories = */ 0,
+ /* priorityCallSenders = */ 0,
+ /* priorityMessageSenders = */ 0,
+ )
+ )
+}
+
+@Module
+interface FakeZenModeRepositoryModule {
+ @Binds fun bindFake(fake: FakeZenModeRepository): ZenModeRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt
new file mode 100644
index 0000000..ea2eeabf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.util
+
+/** A fake [com.android.systemui.util.EventLog] for tests. */
+class FakeEventLog : EventLog {
+ data class Event(val tag: Int, val value: Any)
+
+ private val _events: MutableList<Event> = mutableListOf()
+ val events: List<Event>
+ get() = _events
+
+ fun clear() {
+ _events.clear()
+ }
+
+ override fun writeEvent(tag: Int, value: Int): Int {
+ _events.add(Event(tag, value))
+ return 1
+ }
+
+ override fun writeEvent(tag: Int, value: Long): Int {
+ _events.add(Event(tag, value))
+ return 1
+ }
+
+ override fun writeEvent(tag: Int, value: Float): Int {
+ _events.add(Event(tag, value))
+ return 1
+ }
+
+ override fun writeEvent(tag: Int, value: String): Int {
+ _events.add(Event(tag, value))
+ return 1
+ }
+
+ override fun writeEvent(tag: Int, vararg values: Any): Int {
+ _events.add(Event(tag, values))
+ return 1
+ }
+}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 4c224fb..fb521e1 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -359,7 +359,7 @@
}
@Test
- public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
+ public void testUpdateWallpaperComponent_systemAndLock() throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
/* which */ FLAG_LOCK | FLAG_SYSTEM);
@@ -377,7 +377,7 @@
}
@Test
- public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+ public void testUpdateWallpaperComponent_systemOnly()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
@@ -617,7 +617,7 @@
mWallpaperBackupAgent.onRestoreFinished();
- // wallpaper will be applied to home & lock screen, a success for both screens in expected
+ // wallpaper will be applied to home & lock screen, a success for both screens is expected
DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
assertThat(result).isNotNull();
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6d82b74..f73b00c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -26,7 +26,6 @@
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK;
import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK;
@@ -69,7 +68,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Trace;
import android.provider.Settings;
import android.util.Pair;
import android.util.Slog;
@@ -93,7 +91,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
-import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
@@ -101,7 +98,6 @@
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.magnification.MagnificationProcessor;
-import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -1993,20 +1989,7 @@
}
}
- private void createImeSessionInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
- try {
- if (svcClientTracingEnabled()) {
- logTraceSvcClient("createImeSession", "");
- }
- AccessibilityCallback callback = new AccessibilityCallback();
- listener.createImeSession(callback);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
- }
- }
+ protected void createImeSessionInternal() {
}
private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
@@ -2658,21 +2641,6 @@
}
}
- private static final class AccessibilityCallback
- extends IAccessibilityInputMethodSessionCallback.Stub {
- @Override
- public void sessionCreated(IAccessibilityInputMethodSession session, int id) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AACS.sessionCreated");
- final long ident = Binder.clearCallingIdentity();
- try {
- InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
- }
-
@Override
public void attachAccessibilityOverlayToDisplay(
int interactionId,
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index bb50a99..b5e8c84 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5378,19 +5378,37 @@
@Override
public void requestImeLocked(AbstractAccessibilityServiceConnection connection) {
+ if (!(connection instanceof AccessibilityServiceConnection)
+ || (connection instanceof ProxyAccessibilityServiceConnection)) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "The connection should be a real connection but was "
+ + connection);
+ }
+ return;
+ }
+ AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection;
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::createSessionForConnection, this, connection));
+ AccessibilityManagerService::createSessionForConnection, this, realConnection));
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::bindAndStartInputForConnection, this, connection));
+ AccessibilityManagerService::bindAndStartInputForConnection, this, realConnection));
}
@Override
public void unbindImeLocked(AbstractAccessibilityServiceConnection connection) {
+ if (!(connection instanceof AccessibilityServiceConnection)
+ || (connection instanceof ProxyAccessibilityServiceConnection)) {
+ if (DEBUG) {
+ Slog.d(LOG_TAG, "The connection should be a real connection but was "
+ + connection);
+ }
+ return;
+ }
+ AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection;
mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::unbindInputForConnection, this, connection));
+ AccessibilityManagerService::unbindInputForConnection, this, realConnection));
}
- private void createSessionForConnection(AbstractAccessibilityServiceConnection connection) {
+ private void createSessionForConnection(AccessibilityServiceConnection connection) {
synchronized (mLock) {
if (mInputSessionRequested) {
connection.createImeSessionLocked();
@@ -5398,7 +5416,7 @@
}
}
- private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) {
+ private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) {
synchronized (mLock) {
if (mInputBound) {
connection.bindInputLocked();
@@ -5407,8 +5425,9 @@
}
}
- private void unbindInputForConnection(AbstractAccessibilityServiceConnection connection) {
- InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId);
+ private void unbindInputForConnection(AccessibilityServiceConnection connection) {
+ InputMethodManagerInternal.get()
+ .unbindAccessibilityFromCurrentClient(connection.mId, connection.mUserId);
synchronized (mLock) {
connection.unbindInputLocked();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 7a2a602..40ca694 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -18,6 +18,7 @@
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -27,6 +28,8 @@
import android.accessibilityservice.AccessibilityTrace;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.TouchInteractionController;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -38,12 +41,15 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.Display;
import android.view.MotionEvent;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -70,13 +76,38 @@
Having the reference be null when being called is a very bad sign, but we check the condition.
*/
final WeakReference<AccessibilityUserState> mUserStateWeakReference;
+ @UserIdInt
+ final int mUserId;
final Intent mIntent;
final ActivityTaskManagerInternal mActivityTaskManagerService;
private final Handler mMainHandler;
- AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
- ComponentName componentName,
+ private static final class AccessibilityInputMethodSessionCallback
+ extends IAccessibilityInputMethodSessionCallback.Stub {
+ @UserIdInt
+ private final int mUserId;
+
+ AccessibilityInputMethodSessionCallback(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void sessionCreated(IAccessibilityInputMethodSession session, int id) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ASC.sessionCreated");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ InputMethodManagerInternal.get()
+ .onSessionForAccessibilityCreated(id, session, mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
+ }
+
+ AccessibilityServiceConnection(@Nullable AccessibilityUserState userState,
+ Context context, ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
AccessibilityTrace trace, WindowManagerInternal windowManagerInternal,
@@ -86,6 +117,10 @@
securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer,
awm);
mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
+ // the user ID doesn't matter when userState is null, because it is null only when this is a
+ // ProxyAccessibilityServiceConnection, for which it never creates an IME session and uses
+ // the user ID.
+ mUserId = userState == null ? UserHandle.USER_NULL : userState.mUserId;
mIntent = new Intent().setComponent(mComponentName);
mMainHandler = mainHandler;
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -557,6 +592,24 @@
return mRequestImeApis;
}
+ @Override
+ protected void createImeSessionInternal() {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ if (svcClientTracingEnabled()) {
+ logTraceSvcClient("createImeSession", "");
+ }
+ AccessibilityInputMethodSessionCallback
+ callback = new AccessibilityInputMethodSessionCallback(mUserId);
+ listener.createImeSession(callback);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG,
+ "Error requesting IME session from " + mService, re);
+ }
+ }
+ }
+
private void notifyMotionEventInternal(MotionEvent event) {
final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
if (listener != null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
similarity index 98%
rename from services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
rename to services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
index 6c6394f..f0c44d6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
@@ -35,15 +35,15 @@
/**
* A wrapper of {@link IWindowMagnificationConnection}.
*/
-class WindowMagnificationConnectionWrapper {
+class MagnificationConnectionWrapper {
private static final boolean DBG = false;
- private static final String TAG = "WindowMagnificationConnectionWrapper";
+ private static final String TAG = "MagnificationConnectionWrapper";
private final @NonNull IWindowMagnificationConnection mConnection;
private final @NonNull AccessibilityTraceManager mTrace;
- WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection,
+ MagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection,
@NonNull AccessibilityTraceManager trace) {
mConnection = connection;
mTrace = trace;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 816f22f..3ea805b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -60,7 +60,7 @@
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
/**
- * A class to manipulate window magnification through {@link WindowMagnificationConnectionWrapper}
+ * A class to manipulate window magnification through {@link MagnificationConnectionWrapper}
* create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with
* SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}.
* The applied magnification scale is constrained by
@@ -133,7 +133,7 @@
@VisibleForTesting
@GuardedBy("mLock")
@Nullable
- WindowMagnificationConnectionWrapper mConnectionWrapper;
+ MagnificationConnectionWrapper mConnectionWrapper;
@GuardedBy("mLock")
private ConnectionCallback mConnectionCallback;
@GuardedBy("mLock")
@@ -245,7 +245,7 @@
}
}
if (connection != null) {
- mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace);
+ mConnectionWrapper = new MagnificationConnectionWrapper(connection, mTrace);
}
if (mConnectionWrapper != null) {
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index fb8db21..550e17b 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -21,7 +21,6 @@
defaults: ["platform_service_defaults"],
srcs: [
":services.companion-sources",
- ":VirtualCamera-aidl-sources",
],
libs: [
"app-compat-annotations",
@@ -30,13 +29,6 @@
static_libs: [
"ukey2_jni",
"virtualdevice_flags_lib",
+ "virtual_camera_service_aidl-java",
],
}
-
-filegroup {
- name: "VirtualCamera-aidl-sources",
- srcs: [
- "java/com/android/server/companion/virtual/camera/*.aidl",
- ],
- path: "java",
-}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 562fe3b..118943d 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -51,7 +51,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
-import android.companion.virtual.camera.IVirtualCamera;
+import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
@@ -277,7 +277,7 @@
runningAppsChangedCallback,
params,
DisplayManagerGlobal.getInstance(),
- Flags.virtualCamera() ? new VirtualCameraController(context) : null);
+ Flags.virtualCamera() ? new VirtualCameraController() : null);
}
@VisibleForTesting
@@ -692,7 +692,8 @@
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
- config.getProductId(), deviceToken, config.getAssociatedDisplayId());
+ config.getProductId(), deviceToken,
+ getTargetDisplayIdForInput(config.getAssociatedDisplayId()));
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -710,7 +711,8 @@
final long ident = Binder.clearCallingIdentity();
try {
mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
- config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ config.getProductId(), deviceToken,
+ getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
config.getLanguageTag(), config.getLayoutType());
} finally {
Binder.restoreCallingIdentity(ident);
@@ -776,7 +778,8 @@
try {
mInputController.createNavigationTouchpad(
config.getInputDeviceName(), config.getVendorId(),
- config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ config.getProductId(), deviceToken,
+ getTargetDisplayIdForInput(config.getAssociatedDisplayId()),
touchpadHeight, touchpadWidth);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -954,13 +957,28 @@
}
}
+ @Override // Binder call
@EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- public void registerVirtualCamera(@NonNull IVirtualCamera camera) {
+ public void registerVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
+ throws RemoteException {
super.registerVirtualCamera_enforcePermission();
+ Objects.requireNonNull(cameraConfig);
if (mVirtualCameraController == null) {
- return;
+ throw new UnsupportedOperationException("Virtual camera controller is not available");
}
- mVirtualCameraController.registerCamera(Objects.requireNonNull(camera));
+ mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig));
+ }
+
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void unregisterVirtualCamera(@NonNull VirtualCameraConfig cameraConfig)
+ throws RemoteException {
+ super.unregisterVirtualCamera_enforcePermission();
+ Objects.requireNonNull(cameraConfig);
+ if (mVirtualCameraController == null) {
+ throw new UnsupportedOperationException("Virtual camera controller is not available");
+ }
+ mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig));
}
@Override
@@ -987,6 +1005,20 @@
}
}
+ // For display mirroring, we want to dispatch all key events to the source (default) display,
+ // as the virtual display doesn't have any focused windows. Hence, call this for
+ // associating any input device to the source display if the input device emits any key events.
+ private int getTargetDisplayIdForInput(int displayId) {
+ if (!Flags.interactiveScreenMirror()) {
+ return displayId;
+ }
+
+ DisplayManagerInternal displayManager = LocalServices.getService(
+ DisplayManagerInternal.class);
+ int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId);
+ return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId;
+ }
+
@GuardedBy("mVirtualDeviceLock")
private GenericWindowPolicyController createWindowPolicyControllerLocked(
@NonNull Set<String> displayCategories) {
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
deleted file mode 100644
index a4c1c42..0000000
--- a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.virtual.camera;
-
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-
-/**
- * AIDL Interface to communicate with the VirtualCamera HAL
- * @hide
- */
-interface IVirtualCameraService {
-
- /**
- * Registers a new camera with the virtual camera hal.
- * @return true if the camera was successfully registered
- */
- boolean registerCamera(in IVirtualCamera camera);
-
- /**
- * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't
- * be visible to the camera framework anymore.
- */
- void unregisterCamera(in IVirtualCamera camera);
-}
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 031d949..06be3f3 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -16,207 +16,127 @@
package com.android.server.companion.virtual.camera;
+import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
+import android.os.ServiceManager;
+import android.util.ArraySet;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Set;
/**
* Manages the registration and removal of virtual camera from the server side.
*
* <p>This classes delegate calls to the virtual camera service, so it is dependent on the service
- * to be up and running
+ * to be up and running.
*/
-public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection {
+public final class VirtualCameraController implements IBinder.DeathRecipient {
- private static class VirtualCameraInfo {
-
- private final IVirtualCamera mVirtualCamera;
- private boolean mIsRegistered;
-
- VirtualCameraInfo(IVirtualCamera virtualCamera) {
- mVirtualCamera = virtualCamera;
- }
- }
-
+ private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera";
private static final String TAG = "VirtualCameraController";
- private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera";
- private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService";
- private final Context mContext;
-
- @Nullable private IVirtualCameraService mVirtualCameraService = null;
+ @Nullable private IVirtualCameraService mVirtualCameraService;
@GuardedBy("mCameras")
- private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1);
+ private final Set<VirtualCameraConfig> mCameras = new ArraySet<>();
- public VirtualCameraController(Context context) {
- mContext = context;
+ public VirtualCameraController() {
connectVirtualCameraService();
}
- private void connectVirtualCameraService() {
- final long callingId = Binder.clearCallingIdentity();
+ @VisibleForTesting
+ VirtualCameraController(IVirtualCameraService virtualCameraService) {
+ mVirtualCameraService = virtualCameraService;
+ }
+
+ /**
+ * Register a new virtual camera with the given config.
+ *
+ * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
+ */
+ public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+ // Try to connect to service if not connected already.
+ if (mVirtualCameraService == null) {
+ connectVirtualCameraService();
+ }
+ // Throw exception if we are unable to connect to service.
+ if (mVirtualCameraService == null) {
+ throw new IllegalStateException("Virtual camera service is not connected.");
+ }
+
try {
- Intent intent = new Intent();
- intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE);
- intent.setComponent(
- ComponentName.createRelative(
- VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS));
- mContext.startServiceAsUser(intent, UserHandle.SYSTEM);
- if (!mContext.bindServiceAsUser(
- intent,
- this,
- Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT,
- UserHandle.SYSTEM)) {
- mContext.unbindService(this);
- Log.w(
- TAG,
- "connectVirtualCameraService: Failed to connect to the virtual camera "
- + "service");
- }
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
- private void forwardPendingRegistrations() {
- IVirtualCameraService cameraService = mVirtualCameraService;
- if (cameraService == null) {
- return;
- }
- synchronized (mCameras) {
- for (VirtualCameraInfo cameraInfo : mCameras.values()) {
- if (cameraInfo.mIsRegistered) {
- continue;
- }
- try {
- cameraService.registerCamera(cameraInfo.mVirtualCamera);
- cameraInfo.mIsRegistered = true;
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
- }
- }
- }
-
- /**
- * Remove the virtual camera with the provided name
- *
- * @param camera The name of the camera to remove
- */
- public void unregisterCamera(@NonNull IVirtualCamera camera) {
- IVirtualCameraService virtualCameraService = mVirtualCameraService;
- if (virtualCameraService != null) {
- try {
- virtualCameraService.unregisterCamera(camera);
+ if (registerCameraWithService(cameraConfig)) {
synchronized (mCameras) {
- VirtualCameraInfo cameraInfo = mCameras.remove(camera);
- cameraInfo.mIsRegistered = false;
+ mCameras.add(cameraConfig);
}
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ } else {
+ // TODO(b/310857519): Revisit this to find a better way of indicating failure.
+ throw new RuntimeException("Failed to register virtual camera.");
}
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
}
/**
- * Register a new virtual camera with the provided characteristics.
+ * Unregister the virtual camera with the given config.
*
- * @param camera The {@link IVirtualCamera} producing the image to communicate with the client.
- * @throws IllegalArgumentException if the characteristics could not be parsed.
+ * @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
*/
- public void registerCamera(@NonNull IVirtualCamera camera) {
- IVirtualCameraService service = mVirtualCameraService;
- VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera);
- synchronized (mCameras) {
- mCameras.put(camera, virtualCameraInfo);
- }
- if (service != null) {
- try {
- if (service.registerCamera(camera)) {
- virtualCameraInfo.mIsRegistered = true;
- return;
- }
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) {
+ try {
+ if (mVirtualCameraService == null) {
+ Slog.w(TAG, "Virtual camera service is not connected.");
+ } else {
+ mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder());
}
+ synchronized (mCameras) {
+ mCameras.remove(cameraConfig);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
-
- // Service was not available or registration failed, save the registration for later
- connectVirtualCameraService();
}
@Override
public void binderDied() {
- Log.d(TAG, "binderDied");
+ Slog.d(TAG, "Virtual camera service died.");
mVirtualCameraService = null;
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- mVirtualCameraService = null;
- Log.d(TAG, "onBindingDied() called with: name = [" + name + "]");
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- mVirtualCameraService = null;
- Log.d(TAG, "onNullBinding() called with: name = [" + name + "]");
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Log.d(TAG, "onServiceConnected: " + name.toString());
- mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service);
- try {
- service.linkToDeath(this, 0);
- } catch (RemoteException e) {
- e.rethrowAsRuntimeException();
+ synchronized (mCameras) {
+ mCameras.clear();
}
- forwardPendingRegistrations();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]");
- mVirtualCameraService = null;
}
/** Release resources associated with this controller. */
public void close() {
- if (mVirtualCameraService == null) {
- return;
- }
synchronized (mCameras) {
- mCameras.forEach(
- (name, cameraInfo) -> {
- try {
- mVirtualCameraService.unregisterCamera(name);
- } catch (RemoteException e) {
- Log.w(
- TAG,
- "close(): Camera failed to be removed on camera service.",
- e);
- }
- });
+ if (mVirtualCameraService == null) {
+ Slog.w(TAG, "Virtual camera service is not connected.");
+ } else {
+ for (VirtualCameraConfig config : mCameras) {
+ try {
+ mVirtualCameraService.unregisterCamera(config.getCallback().asBinder());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "close(): Camera failed to be removed on camera "
+ + "service.", e);
+ }
+ }
+ }
+ mCameras.clear();
}
- mContext.unbindService(this);
+ mVirtualCameraService = null;
}
/** Dumps information about this {@link VirtualCameraController} for debugging purposes. */
@@ -226,20 +146,34 @@
fout.printf("%sService:%s\n", indent, mVirtualCameraService);
synchronized (mCameras) {
fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size());
- for (VirtualCameraInfo info : mCameras.values()) {
- VirtualCameraHalConfig config = null;
- try {
- config = info.mVirtualCamera.getHalConfig();
- } catch (RemoteException ex) {
- Log.w(TAG, ex);
- }
- fout.printf(
- "%s- %s isRegistered: %s, token: %s\n",
- indent,
- config == null ? "" : config.displayName,
- info.mIsRegistered,
- info.mVirtualCamera);
+ for (VirtualCameraConfig config : mCameras) {
+ fout.printf("%s token: %s\n", indent, config);
}
}
}
+
+ private void connectVirtualCameraService() {
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ IBinder virtualCameraBinder =
+ ServiceManager.waitForService(VIRTUAL_CAMERA_SERVICE_NAME);
+ if (virtualCameraBinder == null) {
+ Slog.e(TAG, "connectVirtualCameraService: Failed to connect to the virtual "
+ + "camera service");
+ return;
+ }
+ virtualCameraBinder.linkToDeath(this, 0);
+ mVirtualCameraService = IVirtualCameraService.Stub.asInterface(virtualCameraBinder);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException {
+ VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config);
+ return mVirtualCameraService.registerCamera(config.getCallback().asBinder(),
+ serviceConfiguration);
+ }
}
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
new file mode 100644
index 0000000..202f68b
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import android.annotation.NonNull;
+import android.companion.virtual.camera.IVirtualCameraCallback;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.SupportedStreamConfiguration;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.os.RemoteException;
+import android.view.Surface;
+
+/** Utilities to convert the client side classes to the virtual camera service ones. */
+public final class VirtualCameraConversionUtil {
+
+ /**
+ * Fetches the configuration of the provided virtual cameraConfig that was provided by its owner
+ * and convert it into the {@link IVirtualCameraService} types: {@link
+ * VirtualCameraConfiguration}.
+ *
+ * @param cameraConfig The cameraConfig sent by the client.
+ * @return The converted configuration to be sent to the {@link IVirtualCameraService}.
+ * @throws RemoteException If there was an issue fetching the configuration from the client.
+ */
+ @NonNull
+ public static android.companion.virtualcamera.VirtualCameraConfiguration
+ getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig)
+ throws RemoteException {
+ VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration();
+
+ serviceConfiguration.supportedStreamConfigs =
+ cameraConfig.getStreamConfigs().stream()
+ .map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration)
+ .toArray(SupportedStreamConfiguration[]::new);
+
+ serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback());
+ return serviceConfiguration;
+ }
+
+ @NonNull
+ private static android.companion.virtualcamera.IVirtualCameraCallback convertCallback(
+ @NonNull IVirtualCameraCallback camera) {
+ return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() {
+ @Override
+ public void onStreamConfigured(
+ int streamId, Surface surface, int width, int height, int pixelFormat)
+ throws RemoteException {
+ VirtualCameraStreamConfig streamConfig =
+ createStreamConfig(width, height, pixelFormat);
+ camera.onStreamConfigured(streamId, surface, streamConfig);
+ }
+
+ @Override
+ public void onStreamClosed(int streamId) throws RemoteException {
+ camera.onStreamClosed(streamId);
+ }
+ };
+ }
+
+ @NonNull
+ private static VirtualCameraStreamConfig createStreamConfig(
+ int width, int height, int pixelFormat) {
+ return new VirtualCameraStreamConfig(width, height, pixelFormat);
+ }
+
+ @NonNull
+ private static SupportedStreamConfiguration convertSupportedStreamConfiguration(
+ VirtualCameraStreamConfig stream) {
+ SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration();
+ supportedConfig.height = stream.getHeight();
+ supportedConfig.width = stream.getWidth();
+ supportedConfig.pixelFormat = stream.getFormat();
+ return supportedConfig;
+ }
+
+ private VirtualCameraConversionUtil() {
+ }
+}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 4b00434..7e4cf4f 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1421,6 +1421,11 @@
@UserIdInt int userId);
/**
+ * Checks if package is stopped for a specific user.
+ */
+ public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId);
+
+ /**
* Sends the PACKAGE_RESTARTED broadcast.
*/
public abstract void sendPackageRestartedBroadcast(@NonNull String packageName,
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index cd9c53b..80c4c58 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -41,6 +41,18 @@
"file_patterns": ["StorageManagerService\\.java"]
},
{
+ "name": "CtsScopedStorageBypassDatabaseOperationsTest",
+ "file_patterns": ["StorageManagerService\\.java"]
+ },
+ {
+ "name": "CtsScopedStorageGeneralTest",
+ "file_patterns": ["StorageManagerService\\.java"]
+ },
+ {
+ "name": "CtsScopedStorageRedactUriTest",
+ "file_patterns": ["StorageManagerService\\.java"]
+ },
+ {
"name": "FrameworksMockingServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f92af67..b2a7948 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9367,6 +9367,8 @@
*/
void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
final VolatileDropboxEntryStates volatileStates, final StringBuilder sb) {
+ sb.append("SystemUptimeMs: ").append(SystemClock.uptimeMillis()).append("\n");
+
// Watchdog thread ends up invoking this function (with
// a null ProcessRecord) to add the stack file to dropbox.
// Do not acquire a lock on this (am) in such cases, as it
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 9bba08a..ddccce5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2230,8 +2230,10 @@
|| now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
// This service has seen some activity within
// recent memory, so we will keep its process ahead
- // of the background processes.
- if (adj > SERVICE_ADJ) {
+ // of the background processes. This does not apply
+ // to the SDK sandbox process since it should never
+ // be more important than its corresponding app.
+ if (!app.isSdkSandbox && adj > SERVICE_ADJ) {
adj = SERVICE_ADJ;
state.setAdjType("started-services");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 1b48e3c..9f4b3d2 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1058,7 +1058,8 @@
final long identityToken = clearCallingIdentity();
try {
- return getSyncManager().computeSyncable(account, userId, providerName, false);
+ return getSyncManager().computeSyncable(account, userId, providerName, false,
+ /*checkStoppedState=*/ false);
} finally {
restoreCallingIdentity(identityToken);
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index ac7d9c1..575b309 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -438,6 +438,23 @@
}
};
+ private final BroadcastReceiver mForceStoppedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+ // For now, just log when packages were force-stopped and unstopped for debugging.
+ if (isLoggable) {
+ if (Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())) {
+ Log.d(TAG, "Package force-stopped: "
+ + intent.getData().getSchemeSpecificPart());
+ } else if (Intent.ACTION_PACKAGE_UNSTOPPED.equals(intent.getAction())) {
+ Log.d(TAG, "Package unstopped: "
+ + intent.getData().getSchemeSpecificPart());
+ }
+ }
+ }
+ };
+
private final HandlerThread mThread;
private final SyncHandler mSyncHandler;
private final SyncManagerConstants mConstants;
@@ -701,6 +718,12 @@
mContext.registerReceiverAsUser(
mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
+ intentFilter.addDataScheme("package");
+ context.registerReceiver(mForceStoppedReceiver, intentFilter);
+
intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
context.registerReceiver(mOtherIntentsReceiver, intentFilter);
@@ -1108,7 +1131,7 @@
for (String authority : syncableAuthorities) {
int isSyncable = computeSyncable(account.account, account.userId, authority,
- !checkIfAccountReady);
+ !checkIfAccountReady, /*checkStoppedState=*/ true);
if (isSyncable == AuthorityInfo.NOT_SYNCABLE) {
continue;
@@ -1228,7 +1251,7 @@
}
public int computeSyncable(Account account, int userId, String authority,
- boolean checkAccountAccess) {
+ boolean checkAccountAccess, boolean checkStoppedState) {
final int status = getIsSyncable(account, userId, authority);
if (status == AuthorityInfo.NOT_SYNCABLE) {
return AuthorityInfo.NOT_SYNCABLE;
@@ -1241,6 +1264,9 @@
}
final int owningUid = syncAdapterInfo.uid;
final String owningPackage = syncAdapterInfo.componentName.getPackageName();
+ if (checkStoppedState && isPackageStopped(owningPackage, userId)) {
+ return AuthorityInfo.NOT_SYNCABLE;
+ }
if (mAmi.isAppStartModeDisabled(owningUid, owningPackage)) {
Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":"
+ syncAdapterInfo.componentName
@@ -1256,6 +1282,17 @@
return status;
}
+ /**
+ * Returns whether the package is in a stopped state or not.
+ * Always returns {@code false} if the {@code android.content.pm.stay_stopped} flag is not set.
+ */
+ private boolean isPackageStopped(String packageName, int userId) {
+ if (android.content.pm.Flags.stayStopped()) {
+ return mPackageManagerInternal.isPackageStopped(packageName, userId);
+ }
+ return false;
+ }
+
private boolean canAccessAccount(Account account, String packageName, int uid) {
if (mAccountManager.hasAccountAccess(account, packageName,
UserHandle.getUserHandleForUid(uid))) {
@@ -3496,6 +3533,9 @@
for (SyncOperation op: ops) {
if (op.isPeriodic && op.target.matchesSpec(target)
&& op.areExtrasEqual(extras, /*includeSyncSettings=*/ true)) {
+ if (isPackageStopped(op.owningPackage, target.userId)) {
+ continue; // skip stopped package
+ }
maybeUpdateSyncPeriodH(op, pollFrequencyMillis, flexMillis);
return;
}
@@ -3627,7 +3667,8 @@
}
}
// Drop this sync request if it isn't syncable.
- state = computeSyncable(target.account, target.userId, target.provider, true);
+ state = computeSyncable(target.account, target.userId, target.provider, true,
+ /*checkStoppedState=*/ true);
if (state == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) {
if (isLoggable) {
Slog.v(TAG, " Dropping sync operation: "
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d8ac52e..5761c31 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1540,12 +1540,13 @@
performScreenOffTransition = true;
break;
case DisplayPowerRequest.POLICY_DOZE:
- if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+ if (mDozeStateOverride != Display.STATE_UNKNOWN) {
+ state = mDozeStateOverride;
+ } else if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
state = mPowerRequest.dozeScreenState;
} else {
state = Display.STATE_DOZE;
}
- state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride;
if (!mAllowAutoBrightnessWhileDozingConfig) {
brightnessState = mPowerRequest.dozeScreenBrightness;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index d023913..fb6c9e3 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -724,8 +724,9 @@
|| mode.getPhysicalHeight() > maxAllowedHeight
|| mode.getPhysicalWidth() < outSummary.minWidth
|| mode.getPhysicalHeight() < outSummary.minHeight
- || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
- || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
+ || mode.getRefreshRate() < (outSummary.minPhysicalRefreshRate - FLOAT_TOLERANCE)
+ || mode.getRefreshRate() > (outSummary.maxPhysicalRefreshRate + FLOAT_TOLERANCE)
+ ) {
continue;
}
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index 5d6e650..5f28934 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -61,12 +61,13 @@
mPerformScreenOffTransition = true;
break;
case DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE:
- if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+ if (mDozeStateOverride != Display.STATE_UNKNOWN) {
+ state = mDozeStateOverride;
+ } else if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
state = displayPowerRequest.dozeScreenState;
} else {
state = Display.STATE_DOZE;
}
- state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride;
break;
case DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM:
case DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT:
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 3e46ee2..a46d719 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -171,17 +171,20 @@
*
* @param accessibilityConnectionId the connection id of the accessibility service
* @param session the session passed back from the accessibility service
+ * @param userId the user ID to be queried
*/
public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId,
- IAccessibilityInputMethodSession session);
+ IAccessibilityInputMethodSession session, @UserIdInt int userId);
/**
* Unbind the accessibility service with the specified accessibilityConnectionId from current
* client.
*
* @param accessibilityConnectionId the connection id of the accessibility service
+ * @param userId the user ID to be queried
*/
- public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId);
+ public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+ @UserIdInt int userId);
/**
* Switch the keyboard layout in response to a keyboard shortcut.
@@ -266,11 +269,12 @@
@Override
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
- IAccessibilityInputMethodSession session) {
+ IAccessibilityInputMethodSession session, @UserIdInt int userId) {
}
@Override
- public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+ public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+ @UserIdInt int userId) {
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d656f20..6cc0693 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5716,8 +5716,9 @@
@Override
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
- IAccessibilityInputMethodSession session) {
+ IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
+ // TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
mCurClient.mAccessibilitySessions.put(accessibilityConnectionId,
@@ -5744,8 +5745,10 @@
}
@Override
- public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) {
+ public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
+ @UserIdInt int userId) {
synchronized (ImfLock.class) {
+ // TODO(b/305829876): Implement user ID verification
if (mCurClient != null) {
if (DEBUG) {
Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client="
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 550ad5d..392b86e 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -74,7 +74,6 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.HexDump;
import com.android.modules.utils.build.SdkLevel;
-import com.android.net.flags.Flags;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.server.FgThread;
@@ -1062,7 +1061,7 @@
}
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled");
try {
- if (Flags.setDataSaverViaCm()) {
+ if (SdkLevel.isAtLeastV()) {
// setDataSaverEnabled throws if it fails to set data saver.
mContext.getSystemService(ConnectivityManager.class)
.setDataSaverEnabled(enable);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2ec3700..8c75367 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -360,6 +360,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
@@ -5245,13 +5246,23 @@
}
}
+ // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
@Override
public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
- enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
+ enforcePolicyAccess(Binder.getCallingUid(), "getZenRules");
return mZenModeHelper.getZenRules();
}
@Override
+ public Map<String, AutomaticZenRule> getAutomaticZenRules() {
+ if (!android.app.Flags.modesApi()) {
+ throw new IllegalStateException("getAutomaticZenRules called with flag off!");
+ }
+ enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
+ return mZenModeHelper.getAutomaticZenRules();
+ }
+
+ @Override
public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c637df2..a1704c6 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -106,7 +106,9 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -334,6 +336,7 @@
return mZenMode;
}
+ // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
public List<ZenRule> getZenRules() {
List<ZenRule> rules = new ArrayList<>();
synchronized (mConfigLock) {
@@ -347,6 +350,20 @@
return rules;
}
+ /**
+ * Get the list of {@link AutomaticZenRule} instances that the calling package can manage
+ * (which means the owned rules for a regular app, and every rule for system callers) together
+ * with their ids.
+ */
+ Map<String, AutomaticZenRule> getAutomaticZenRules() {
+ List<ZenRule> ruleList = getZenRules();
+ HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
+ for (ZenRule rule : ruleList) {
+ rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
+ }
+ return rules;
+ }
+
public AutomaticZenRule getAutomaticZenRule(String id) {
ZenRule rule;
synchronized (mConfigLock) {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 8bf903a..f45571a 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -578,6 +578,12 @@
? null
: ps.getUserStateOrDefault(nextUserId).getArchiveState();
+ // Preserve firstInstallTime in case of DELETE_KEEP_DATA
+ // For full uninstalls, reset firstInstallTime to 0 as if it has never been installed
+ final long firstInstallTime = (flags & DELETE_KEEP_DATA) == 0
+ ? 0
+ : ps.getUserStateOrDefault(nextUserId).getFirstInstallTimeMillis();
+
ps.setUserState(nextUserId,
ps.getCeDataInode(nextUserId),
ps.getDeDataInode(nextUserId),
@@ -597,7 +603,7 @@
PackageManager.UNINSTALL_REASON_UNKNOWN,
null /*harmfulAppWarning*/,
null /*splashScreenTheme*/,
- 0 /*firstInstallTime*/,
+ firstInstallTime,
PackageManager.USER_MIN_ASPECT_RATIO_UNSET,
archiveState);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index ea783b8..b281808 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -758,6 +758,11 @@
return snapshot().isPackageQuarantinedForUser(packageName, userId);
}
+ @Override
+ public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) {
+ return snapshot().isPackageStoppedForUser(packageName, userId);
+ }
+
@NonNull
@Override
@Deprecated
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 81a570f..4e14c90 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1388,10 +1388,32 @@
final long identity = Binder.clearCallingIdentity();
try {
+ // QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL is only allowed for managed-profiles
+ if (dontAskCredential) {
+ UserInfo userInfo;
+ synchronized (mUsersLock) {
+ userInfo = getUserInfo(userId);
+ }
+ if (!userInfo.isManagedProfile()) {
+ throw new IllegalArgumentException("Invalid flags: " + flags
+ + ". Can't skip credential check for the user");
+ }
+ }
if (enableQuietMode) {
setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
return true;
}
+ if (android.os.Flags.allowPrivateProfile()) {
+ final UserProperties userProperties = getUserPropertiesInternal(userId);
+ if (userProperties != null
+ && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
+ if (onlyIfCredentialNotRequired) {
+ return false;
+ }
+ showConfirmCredentialToDisableQuietMode(userId, target);
+ return false;
+ }
+ }
final boolean hasUnifiedChallenge =
mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId);
if (hasUnifiedChallenge) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 29e0c35..7da76c1 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -193,6 +193,7 @@
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setCredentialShareableWithParent(true));
}
@@ -292,7 +293,8 @@
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setCredentialShareableWithParent(false)
+ .setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
.setMediaSharedWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index cf1036c..72c10cc 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -32,7 +32,6 @@
import static android.os.Build.VERSION_CODES.O;
import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID;
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
-import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.STATE_OFF;
@@ -69,6 +68,7 @@
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
+import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
@@ -101,6 +101,7 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
+import android.app.IActivityManager;
import android.app.IUiModeManager;
import android.app.NotificationManager;
import android.app.ProgressDialog;
@@ -427,6 +428,7 @@
WindowManagerInternal mWindowManagerInternal;
PowerManager mPowerManager;
ActivityManagerInternal mActivityManagerInternal;
+ IActivityManager mActivityManagerService;
ActivityTaskManagerInternal mActivityTaskManagerInternal;
AutofillManagerInternal mAutofillManagerInternal;
InputManager mInputManager;
@@ -549,7 +551,7 @@
int mLidNavigationAccessibility;
int mShortPressOnPowerBehavior;
private boolean mShouldEarlyShortPressOnPower;
- private boolean mShouldEarlyShortPressOnStemPrimary;
+ boolean mShouldEarlyShortPressOnStemPrimary;
int mLongPressOnPowerBehavior;
long mLongPressOnPowerAssistantTimeoutMs;
int mVeryLongPressOnPowerBehavior;
@@ -578,6 +580,7 @@
private int mDoublePressOnStemPrimaryBehavior;
private int mTriplePressOnStemPrimaryBehavior;
private int mLongPressOnStemPrimaryBehavior;
+ private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp;
private boolean mHandleVolumeKeysInWM;
@@ -1563,7 +1566,7 @@
? false
: mKeyguardDelegate.isShowing();
if (!keyguardActive) {
- switchRecentTask();
+ performStemPrimaryDoublePressSwitchToRecentTask();
}
break;
}
@@ -1672,11 +1675,11 @@
/**
* Load most recent task (expect current task) and bring it to the front.
*/
- private void switchRecentTask() {
- RecentTaskInfo targetTask = mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
+ void performStemPrimaryDoublePressSwitchToRecentTask() {
+ RecentTaskInfo targetTask = mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp;
if (targetTask == null) {
if (DEBUG_INPUT) {
- Slog.w(TAG, "No recent task available! Show watch face.");
+ Slog.w(TAG, "No recent task available! Show wallpaper.");
}
goHome();
return;
@@ -1695,7 +1698,7 @@
+ targetTask.baseIntent);
}
try {
- ActivityManager.getService().startActivityFromRecents(targetTask.persistentId, null);
+ mActivityManagerService.startActivityFromRecents(targetTask.persistentId, null);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Failed to start task " + targetTask.persistentId + " from recents", e);
}
@@ -2219,6 +2222,10 @@
}
});
}
+
+ IActivityManager getActivityManagerService() {
+ return ActivityManager.getService();
+ }
}
/** {@inheritDoc} */
@@ -2233,6 +2240,7 @@
mWindowManagerFuncs = injector.getWindowManagerFuncs();
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mActivityManagerService = injector.getActivityManagerService();
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mInputManager = mContext.getSystemService(InputManager.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -2767,8 +2775,17 @@
@Override
void onKeyUp(long eventTime, int count) {
- if (mShouldEarlyShortPressOnStemPrimary && count == 1) {
- stemPrimaryPress(1 /*pressCount*/);
+ if (count == 1) {
+ // Save info about the most recent task on the first press of the stem key. This
+ // may be used later to switch to the most recent app using double press gesture.
+ // It is possible that we may navigate away from this task before the double
+ // press is detected, as a result of the first press, so we save the current
+ // most recent task before that happens.
+ mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp =
+ mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
+ if (mShouldEarlyShortPressOnStemPrimary) {
+ stemPrimaryPress(1 /*pressCount*/);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index dd39fb0..ee3b746 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -32,6 +32,8 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.WorkDuration;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseIntArray;
@@ -195,6 +197,9 @@
private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
+ private static native void nativeReportActualWorkDuration(long halPtr,
+ WorkDuration[] workDurations);
+
/** Wrapper for HintManager.nativeInit */
public void halInit() {
nativeInit();
@@ -252,6 +257,10 @@
nativeSetMode(halPtr, mode, enabled);
}
+ /** Wrapper for HintManager.nativeReportActualWorkDuration */
+ public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) {
+ nativeReportActualWorkDuration(halPtr, workDurations);
+ }
}
@VisibleForTesting
@@ -624,6 +633,52 @@
}
}
+ @Override
+ public void reportActualWorkDuration2(WorkDuration[] workDurations) {
+ synchronized (this) {
+ if (mHalSessionPtr == 0 || !mUpdateAllowed) {
+ return;
+ }
+ Preconditions.checkArgument(workDurations.length != 0, "the count"
+ + " of work durations shouldn't be 0.");
+ for (WorkDuration workDuration : workDurations) {
+ validateWorkDuration(workDuration);
+ }
+ mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations);
+ }
+ }
+
+ void validateWorkDuration(WorkDuration workDuration) {
+ if (DEBUG) {
+ Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", "
+ + workDuration.getWorkPeriodStartTimestampNanos() + ", "
+ + workDuration.getActualTotalDurationNanos() + ", "
+ + workDuration.getActualCpuDurationNanos() + ", "
+ + workDuration.getActualGpuDurationNanos() + ")");
+ }
+ if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple(
+ "Work period start timestamp (%d) should be greater than 0",
+ workDuration.getWorkPeriodStartTimestampNanos()));
+ }
+ if (workDuration.getActualTotalDurationNanos() <= 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple("Actual total duration (%d) should be greater than 0",
+ workDuration.getActualTotalDurationNanos()));
+ }
+ if (workDuration.getActualCpuDurationNanos() <= 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0",
+ workDuration.getActualCpuDurationNanos()));
+ }
+ if (workDuration.getActualGpuDurationNanos() < 0) {
+ throw new IllegalArgumentException(
+ TextUtils.formatSimple("Actual GPU duration (%d) should be non negative",
+ workDuration.getActualGpuDurationNanos()));
+ }
+ }
+
private void onProcStateChanged(boolean updateAllowed) {
updateHintAllowed(updateAllowed);
}
diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
index 6d580e9..8b57f87 100644
--- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java
@@ -280,12 +280,12 @@
return null;
}
- if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
+ if (getSessionCountByUidLocked(callingUid) == MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) {
Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid);
Counter.logIncrementWithUid(
"speech_recognition.value_exceed_session_count",
callingUid);
- return null;
+ // TODO(b/297249772): return null early to refuse the new connection
}
if (servicesForClient != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index bdcde66..f8078d2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -869,8 +869,7 @@
if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ ", reverting to built-in wallpaper!");
- int which = mWallpaper.mWhich;
- clearWallpaperLocked(which, mWallpaper.userId, false, null);
+ clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
}
}
};
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index b3ae2ee..b1abe2a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -558,7 +558,7 @@
}
if (newTarget != null) {
int displayId = newTarget.getDisplayId();
- IBinder clientBinder = newTarget.getIWindow().asBinder();
+ IBinder clientBinder = newTarget.getWindowToken();
mFocusedWindow.put(displayId, clientBinder);
}
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index cdd1a269..3cf19dd 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -34,7 +34,6 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.view.IWindow;
import android.view.InputWindowHandle;
import android.view.MagnificationSpec;
import android.view.WindowInfo;
@@ -197,14 +196,14 @@
final HashMap<IBinder, Matrix> windowsTransformMatrixMap = new HashMap<>();
for (InputWindowHandle inputWindowHandle : windows) {
- final IWindow iWindow = inputWindowHandle.getWindow();
- final WindowState windowState = iWindow != null ? mService.mWindowMap.get(
- iWindow.asBinder()) : null;
+ final IBinder iWindow = inputWindowHandle.getWindowToken();
+ final WindowState windowState = iWindow != null ? mService.mWindowMap.get(iWindow)
+ : null;
if (windowState != null && windowState.shouldMagnify()) {
final Matrix transformMatrix = new Matrix();
windowState.getTransformationMatrix(sTempFloats, transformMatrix);
- windowsTransformMatrixMap.put(iWindow.asBinder(), transformMatrix);
+ windowsTransformMatrixMap.put(iWindow, transformMatrix);
}
}
@@ -330,8 +329,8 @@
// the old and new windows at the same index should be the
// same, otherwise something changed.
for (int i = 0; i < windowsCount; i++) {
- final IWindow newWindowToken = newWindows.get(i).getWindow();
- final IWindow oldWindowToken = oldWindows.get(i).getWindow();
+ final IBinder newWindowToken = newWindows.get(i).getWindowToken();
+ final IBinder oldWindowToken = oldWindows.get(i).getWindowToken();
final boolean hasNewWindowToken = newWindowToken != null;
final boolean hasOldWindowToken = oldWindowToken != null;
@@ -342,8 +341,7 @@
// If both old and new windows had window tokens, but those tokens differ,
// then the windows have changed.
- if (hasNewWindowToken && hasOldWindowToken
- && !newWindowToken.asBinder().equals(oldWindowToken.asBinder())) {
+ if (hasNewWindowToken && hasOldWindowToken && !newWindowToken.equals(oldWindowToken)) {
return true;
}
}
@@ -393,9 +391,7 @@
for (int index = inputWindowHandles.size() - 1; index >= 0; index--) {
final Matrix windowTransformMatrix = mTempMatrix2;
final InputWindowHandle windowHandle = inputWindowHandles.get(index);
- final IBinder iBinder =
- windowHandle.getWindow() != null ? windowHandle.getWindow().asBinder() : null;
-
+ final IBinder iBinder = windowHandle.getWindowToken();
if (getWindowTransformMatrix(iBinder, windowTransformMatrix)) {
generateMagnificationSpecInverseMatrix(windowHandle, currentMagnificationSpec,
previousMagnificationSpec, windowTransformMatrix);
@@ -645,7 +641,7 @@
*/
public static class AccessibilityWindow {
// Data
- private IWindow mWindow;
+ private IBinder mWindow;
private int mDisplayId;
@WindowManager.LayoutParams.WindowType
private int mType;
@@ -670,9 +666,8 @@
public static AccessibilityWindow initializeData(WindowManagerService service,
InputWindowHandle inputWindowHandle, Matrix magnificationInverseMatrix,
IBinder pipIBinder, Matrix displayMatrix) {
- final IWindow window = inputWindowHandle.getWindow();
- final WindowState windowState = window != null ? service.mWindowMap.get(
- window.asBinder()) : null;
+ final IBinder window = inputWindowHandle.getWindowToken();
+ final WindowState windowState = window != null ? service.mWindowMap.get(window) : null;
final AccessibilityWindow instance = new AccessibilityWindow();
@@ -680,7 +675,7 @@
instance.mDisplayId = inputWindowHandle.displayId;
instance.mInputConfig = inputWindowHandle.inputConfig;
instance.mType = inputWindowHandle.layoutParamsType;
- instance.mIsPIPMenu = window != null && window.asBinder().equals(pipIBinder);
+ instance.mIsPIPMenu = window != null && window.equals(pipIBinder);
// TODO (b/199357848): gets the private flag of the window from other way.
instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0;
@@ -867,7 +862,7 @@
WindowInfo windowInfo = WindowInfo.obtain();
windowInfo.displayId = window.mDisplayId;
windowInfo.type = window.mType;
- windowInfo.token = window.mWindow != null ? window.mWindow.asBinder() : null;
+ windowInfo.token = window.mWindow;
windowInfo.hasFlagWatchOutsideTouch = (window.mInputConfig
& InputConfig.WATCH_OUTSIDE_TOUCH) != 0;
// Set it to true to be consistent with the legacy implementation.
@@ -878,7 +873,7 @@
@Override
public String toString() {
String windowToken =
- mWindow != null ? mWindow.asBinder().toString() : "(no window token)";
+ mWindow != null ? mWindow.toString() : "(no window token)";
return "A11yWindow=[" + windowToken
+ ", displayId=" + mDisplayId
+ ", inputConfig=0x" + Integer.toHexString(mInputConfig)
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 26f0d34..7b399c8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1018,9 +1018,8 @@
}
try {
- final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread());
- transaction.addCallback(EnterPipRequestedItem.obtain(r.token));
- mService.getLifecycleManager().scheduleTransaction(transaction);
+ mService.getLifecycleManager().scheduleTransaction(r.app.getThread(),
+ EnterPipRequestedItem.obtain(r.token));
return true;
} catch (Exception e) {
Slog.w(TAG, "Failed to send enter pip requested item: "
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 777b5cd..e196d46 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -936,7 +936,7 @@
final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
clientTransaction.addCallback(LaunchActivityItem.obtain(r.token,
- new Intent(r.intent), System.identityHashCode(r), r.info,
+ r.intent, System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 87ae045..43f3209 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -39,7 +39,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -60,7 +59,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.LocalServices;
import com.android.server.wm.utils.InsetUtils;
import java.io.PrintWriter;
@@ -151,97 +149,77 @@
// Don't start any animation for it.
return null;
}
- WindowManagerInternal windowManagerInternal =
- LocalServices.getService(WindowManagerInternal.class);
- IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken();
window = wmService.getFocusedWindowLocked();
if (window == null) {
- EmbeddedWindowController.EmbeddedWindow embeddedWindow =
- wmService.mEmbeddedWindowController.getByInputTransferToken(
- focusedWindowToken);
- if (embeddedWindow != null) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Current focused window is embeddedWindow. Dispatch KEYCODE_BACK.");
- return null;
- }
- }
-
- // Lets first gather the states of things
- // - What is our current window ?
- // - Does it has an Activity and a Task ?
- // TODO Temp workaround for Sysui until b/221071505 is fixed
- if (window != null) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Focused window found using getFocusedWindowToken");
- }
-
- if (window != null) {
- // This is needed to bridge the old and new back behavior with recents. While in
- // Overview with live tile enabled, the previous app is technically focused but we
- // add an input consumer to capture all input that would otherwise go to the apps
- // being controlled by the animation. This means that the window resolved is not
- // the right window to consume back while in overview, so we need to route it to
- // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
- // compat callback in VRI only works when the window is focused.
- // This symptom also happen while shell transition enabled, we can check that by
- // isTransientLaunch to know whether the focus window is point to live tile.
- final RecentsAnimationController recentsAnimationController =
- wmService.getRecentsAnimationController();
- final ActivityRecord ar = window.mActivityRecord;
- if ((ar != null && ar.isActivityTypeHomeOrRecents()
- && ar.mTransitionController.isTransientLaunch(ar))
- || (recentsAnimationController != null
- && recentsAnimationController.shouldApplyInputConsumer(ar))) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
- + "recents. Overriding back callback to recents controller callback.");
- return null;
- }
-
- if (!window.isDrawn()) {
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
- "Focused window didn't have a valid surface drawn.");
- return null;
- }
- }
-
- if (window == null) {
// We don't have any focused window, fallback ont the top currentTask of the focused
// display.
ProtoLog.w(WM_DEBUG_BACK_PREVIEW,
"No focused window, defaulting to top current task's window");
currentTask = wmService.mAtmService.getTopDisplayFocusedRootTask();
- window = currentTask.getWindow(WindowState::isFocused);
+ window = currentTask != null
+ ? currentTask.getWindow(WindowState::isFocused) : null;
}
- // Now let's find if this window has a callback from the client side.
- OnBackInvokedCallbackInfo callbackInfo = null;
- if (window != null) {
- currentActivity = window.mActivityRecord;
- currentTask = window.getTask();
- callbackInfo = window.getOnBackInvokedCallbackInfo();
- if (callbackInfo == null) {
- Slog.e(TAG, "No callback registered, returning null.");
- return null;
- }
- if (!callbackInfo.isSystemCallback()) {
- backType = BackNavigationInfo.TYPE_CALLBACK;
- }
- infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
- infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
- mNavigationMonitor.startMonitor(window, navigationObserver);
- }
-
- ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
- + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
- currentTask, currentActivity, callbackInfo, window);
-
if (window == null) {
Slog.e(TAG, "Window is null, returning null.");
return null;
}
+ // This is needed to bridge the old and new back behavior with recents. While in
+ // Overview with live tile enabled, the previous app is technically focused but we
+ // add an input consumer to capture all input that would otherwise go to the apps
+ // being controlled by the animation. This means that the window resolved is not
+ // the right window to consume back while in overview, so we need to route it to
+ // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing
+ // compat callback in VRI only works when the window is focused.
+ // This symptom also happen while shell transition enabled, we can check that by
+ // isTransientLaunch to know whether the focus window is point to live tile.
+ final RecentsAnimationController recentsAnimationController =
+ wmService.getRecentsAnimationController();
+ final ActivityRecord tmpAR = window.mActivityRecord;
+ if ((tmpAR != null && tmpAR.isActivityTypeHomeOrRecents()
+ && tmpAR.mTransitionController.isTransientLaunch(tmpAR))
+ || (recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(tmpAR))) {
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by "
+ + "recents. Overriding back callback to recents controller callback.");
+ return null;
+ }
+
+ if (!window.isDrawn()) {
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Focused window didn't have a valid surface drawn.");
+ return null;
+ }
+
+ currentActivity = window.mActivityRecord;
+ currentTask = window.getTask();
+ if ((currentTask != null && !currentTask.isVisibleRequested())
+ || (currentActivity != null && !currentActivity.isVisibleRequested())) {
+ // Closing transition is happening on focus window and should be update soon,
+ // don't drive back navigation with it.
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing.");
+ return null;
+ }
+ // Now let's find if this window has a callback from the client side.
+ final OnBackInvokedCallbackInfo callbackInfo = window.getOnBackInvokedCallbackInfo();
+ if (callbackInfo == null) {
+ Slog.e(TAG, "No callback registered, returning null.");
+ return null;
+ }
+ if (!callbackInfo.isSystemCallback()) {
+ backType = BackNavigationInfo.TYPE_CALLBACK;
+ }
+ infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
+ infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
+ mNavigationMonitor.startMonitor(window, navigationObserver);
+
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
+ + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
+ currentTask, currentActivity, callbackInfo, window);
+
// If we don't need to set up the animation, we return early. This is the case when
// - We have an application callback.
// - We don't have any ActivityRecord or Task to animate.
@@ -322,12 +300,13 @@
}
return false;
}, currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/);
- final ActivityRecord tmpPre = prevTask.getTopNonFinishingActivity();
+ final ActivityRecord tmpPre = prevTask != null
+ ? prevTask.getTopNonFinishingActivity() : null;
if (tmpPre != null) {
prevActivities.add(tmpPre);
findAdjacentActivityIfExist(tmpPre, prevActivities);
}
- if (prevActivities.isEmpty()
+ if (prevTask == null || prevActivities.isEmpty()
|| (isOccluded && !prevActivities.get(0).canShowWhenLocked())) {
backType = BackNavigationInfo.TYPE_CALLBACK;
} else if (prevTask.isActivityTypeHome()) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8a7cc67..b34f912 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1877,15 +1877,12 @@
final InsetsState insetsState = df.mInsetsState;
final Rect displayFrame = insetsState.getDisplayFrame();
final Insets decor = insetsState.calculateInsets(displayFrame,
- dc.mWmService.mDecorTypes,
- true /* ignoreVisibility */);
- final Insets statusBar = insetsState.calculateInsets(displayFrame,
- Type.statusBars(), true /* ignoreVisibility */);
+ dc.mWmService.mDecorTypes, true /* ignoreVisibility */);
+ final Insets configInsets = insetsState.calculateInsets(displayFrame,
+ dc.mWmService.mConfigTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
- mConfigInsets.set(Math.max(statusBar.left, decor.left),
- Math.max(statusBar.top, decor.top),
- Math.max(statusBar.right, decor.right),
- Math.max(statusBar.bottom, decor.bottom));
+ mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
+ configInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 1462878..1670b36e 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -71,7 +71,7 @@
mWindowsByInputTransferToken.put(inputTransferToken, window);
mWindowsByWindowToken.put(window.getWindowToken(), window);
updateProcessController(window);
- window.mClient.asBinder().linkToDeath(()-> {
+ window.mClient.linkToDeath(()-> {
synchronized (mGlobalLock) {
mWindows.remove(inputToken);
mWindowsByInputTransferToken.remove(inputTransferToken);
@@ -103,7 +103,7 @@
void remove(IWindow client) {
for (int i = mWindows.size() - 1; i >= 0; i--) {
EmbeddedWindow ew = mWindows.valueAt(i);
- if (ew.mClient.asBinder() == client.asBinder()) {
+ if (ew.mClient == client.asBinder()) {
mWindows.removeAt(i).onRemoved();
mWindowsByInputTransferToken.remove(ew.getInputTransferToken());
mWindowsByWindowToken.remove(ew.getWindowToken());
@@ -136,7 +136,7 @@
}
static class EmbeddedWindow implements InputTarget {
- final IWindow mClient;
+ final IBinder mClient;
@Nullable final WindowState mHostWindowState;
@Nullable final ActivityRecord mHostActivityRecord;
final String mName;
@@ -169,7 +169,7 @@
* @param windowType to forward to input
* @param displayId used for focus requests
*/
- EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken,
+ EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken,
WindowState hostWindowState, int ownerUid, int ownerPid, int windowType,
int displayId, IBinder inputTransferToken, String inputHandleName,
boolean isFocusable) {
@@ -241,13 +241,8 @@
return mWmService.mRoot.getDisplayContent(getDisplayId());
}
- @Override
- public IWindow getIWindow() {
- return mClient;
- }
-
public IBinder getWindowToken() {
- return mClient.asBinder();
+ return mClient;
}
@Override
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 61fea4d..c8fd16d 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -57,7 +57,6 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
-import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
import android.view.InputChannel;
@@ -74,7 +73,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Set;
import java.util.function.Consumer;
final class InputMonitor {
@@ -258,7 +256,7 @@
inputWindowHandle.setDispatchingTimeoutMillis(w.getInputDispatchingTimeoutMillis());
inputWindowHandle.setTouchOcclusionMode(w.getTouchOcclusionMode());
inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
- inputWindowHandle.setWindowToken(w.mClient);
+ inputWindowHandle.setWindowToken(w.mClient.asBinder());
inputWindowHandle.setName(w.getName());
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
index 653f5f5..baf0db2 100644
--- a/services/core/java/com/android/server/wm/InputTarget.java
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -16,8 +16,8 @@
package com.android.server.wm;
+import android.os.IBinder;
import android.util.proto.ProtoOutputStream;
-import android.view.IWindow;
/**
* Common interface between focusable objects.
@@ -33,7 +33,7 @@
int getDisplayId();
/* Client IWindow for the target. */
- IWindow getIWindow();
+ IBinder getWindowToken();
/* Owning pid of the target. */
int getPid();
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 90d81bd..b748053 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -21,7 +21,6 @@
import android.graphics.Region;
import android.os.IBinder;
import android.os.InputConfig;
-import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.InputWindowHandle;
import android.view.InputWindowHandle.InputConfigFlags;
@@ -264,8 +263,8 @@
mChanged = true;
}
- void setWindowToken(IWindow windowToken) {
- if (mHandle.getWindow() == windowToken) {
+ void setWindowToken(IBinder windowToken) {
+ if (mHandle.getWindowToken() == windowToken) {
return;
}
mHandle.setWindowToken(windowToken);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0c55d8a..18d64d7 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -894,7 +894,7 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
- IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type,
+ IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int type,
int inputFeatures, IBinder windowToken, IBinder inputTransferToken,
String inputHandleName, InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
@@ -905,8 +905,8 @@
final long identity = Binder.clearCallingIdentity();
try {
- mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken,
- flags, mCanAddInternalSystemWindow ? privateFlags : 0,
+ mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
+ hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
outInputChannel);
} finally {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3e23fab..f700944 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -173,7 +173,6 @@
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
private final Token mToken;
- private IApplicationThread mRemoteAnimApp;
private @Nullable ActivityRecord mPipActivity;
@@ -1485,13 +1484,14 @@
return mForcePlaying;
}
+ /** Adjusts the priority of the process which will run the transition animation. */
void setRemoteAnimationApp(IApplicationThread app) {
- mRemoteAnimApp = app;
- }
-
- /** Returns the app which will run the transition animation. */
- IApplicationThread getRemoteAnimationApp() {
- return mRemoteAnimApp;
+ final WindowProcessController wpc = mController.mAtm.getProcessController(app);
+ if (wpc != null) {
+ // This is an early prediction. If the process doesn't ack the animation in 200 ms,
+ // the priority will be restored.
+ mController.mRemotePlayer.update(wpc, true /* running */, true /* predict */);
+ }
}
void setNoAnimation(WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1f01778..a736874 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1251,13 +1251,7 @@
} else if (mPlayingTransitions.isEmpty()) {
mTransitionPlayerProc.setRunningRemoteAnimation(false);
mRemotePlayer.clear();
- return;
}
- final IApplicationThread appThread = transition.getRemoteAnimationApp();
- if (appThread == null || appThread == mTransitionPlayerProc.getThread()) return;
- final WindowProcessController delegate = mAtm.getProcessController(appThread);
- if (delegate == null) return;
- mRemotePlayer.update(delegate, isPlaying, true /* predict */);
}
/** Called when a transition is aborted. This should only be called by {@link Transition} */
@@ -1483,7 +1477,7 @@
* {@link #mTransitionPlayerProc}.
*/
static class RemotePlayer {
- private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100;
+ private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 200;
@GuardedBy("itself")
private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>();
private final ActivityTaskManagerService mAtm;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f6c431f..2b77fff 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3950,7 +3950,7 @@
return true;
}
- boolean useBLASTSync() {
+ boolean syncNextBuffer() {
return mSyncState != SYNC_STATE_NONE;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0a986c8..757d6d6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -660,11 +660,6 @@
@NonNull
final RootWindowContainer mRoot;
- // Whether the system should use BLAST for ViewRootImpl
- final boolean mUseBLAST;
- // Whether to enable BLASTSyncEngine Transaction passing.
- static final boolean USE_BLAST_SYNC = true;
-
final BLASTSyncEngine mSyncEngine;
boolean mIsPc;
@@ -1196,7 +1191,8 @@
&& mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
- mConfigTypes = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
+ mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
} else {
mDecorTypes = WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.navigationBars();
@@ -1219,8 +1215,6 @@
mRoot = new RootWindowContainer(this);
final ContentResolver resolver = context.getContentResolver();
- mUseBLAST = Settings.Global.getInt(resolver,
- Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, 1) == 1;
mSyncEngine = new BLASTSyncEngine(this);
@@ -1741,13 +1735,6 @@
}
// From now on, no exceptions or errors allowed!
-
- res = ADD_OKAY;
-
- if (mUseBLAST) {
- res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST;
- }
-
if (displayContent.mCurrentFocus == null) {
displayContent.mWinAddedSinceNullFocus.add(win);
}
@@ -2555,7 +2542,7 @@
if (outSyncIdBundle != null) {
final int maybeSyncSeqId;
- if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility == View.VISIBLE
+ if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
&& win.mSyncSeqId > lastSyncSeqId) {
maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1;
win.markRedrawForSyncReported();
@@ -5818,15 +5805,6 @@
}
@Override
- public boolean useBLAST() {
- return mUseBLAST;
- }
-
- public boolean useBLASTSync() {
- return USE_BLAST_SYNC;
- }
-
- @Override
public void getInitialDisplaySize(int displayId, Point size) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
@@ -8922,7 +8900,7 @@
* views.
*/
void grantInputChannel(Session session, int callingUid, int callingPid, int displayId,
- SurfaceControl surface, IWindow window, IBinder hostInputToken,
+ SurfaceControl surface, IBinder clientToken, IBinder hostInputToken,
int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken,
IBinder inputTransferToken, String inputHandleName, InputChannel outInputChannel) {
final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
@@ -8931,7 +8909,7 @@
Objects.requireNonNull(outInputChannel);
synchronized (mGlobalLock) {
EmbeddedWindowController.EmbeddedWindow win =
- new EmbeddedWindowController.EmbeddedWindow(session, this, window,
+ new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken,
mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
sanitizedType, displayId, inputTransferToken, inputHandleName,
(flags & FLAG_NOT_FOCUSABLE) == 0);
@@ -8943,7 +8921,7 @@
updateInputChannel(outInputChannel.getToken(), callingUid, callingPid, displayId, surface,
name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
- null /* region */, window);
+ null /* region */, clientToken);
}
boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) {
@@ -9018,10 +8996,10 @@
private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid,
int displayId, SurfaceControl surface, String name,
InputApplicationHandle applicationHandle, int flags,
- int privateFlags, int inputFeatures, int type, Region region, IWindow window) {
+ int privateFlags, int inputFeatures, int type, Region region, IBinder clientToken) {
final InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId);
h.token = channelToken;
- h.setWindowToken(window);
+ h.setWindowToken(clientToken);
h.name = name;
flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e17011..5293292 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1693,8 +1693,8 @@
}
@Override
- public IWindow getIWindow() {
- return mClient;
+ public IBinder getWindowToken() {
+ return mClient.asBinder();
}
@Override
@@ -3368,7 +3368,7 @@
mAnimatingExit = false;
ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this);
- if (useBLASTSync()) {
+ if (syncNextBuffer()) {
immediatelyNotifyBlastSync();
}
}
@@ -5799,7 +5799,7 @@
// Consume the transaction because the sync group will merge it.
postDrawTransaction = null;
}
- } else if (useBLASTSync()) {
+ } else if (syncNextBuffer()) {
// Sync that is not using BLAST
layoutNeeded = onSyncFinishedDrawing();
}
@@ -5855,7 +5855,7 @@
// drawing for being visible, then no need to request redraw.
return false;
}
- return useBLASTSync();
+ return syncNextBuffer();
}
int getSyncMethod() {
@@ -5880,11 +5880,11 @@
* it's next draw in to a transaction). If we have pending draw handlers, we are
* looking for the client to sync.
*
- * See {@link WindowState#mPendingDrawHandlers}
+ * See {@link WindowState#mDrawHandlers}
*/
@Override
- boolean useBLASTSync() {
- return super.useBLASTSync() || (mDrawHandlers.size() != 0);
+ boolean syncNextBuffer() {
+ return super.syncNextBuffer() || (mDrawHandlers.size() != 0);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 209d934..6c15c22 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -37,7 +37,6 @@
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.WindowContentFrameStats;
-import android.view.WindowManager;
import com.android.internal.protolog.common.ProtoLog;
@@ -73,7 +72,7 @@
mWindowSession = win.mSession;
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
- final SurfaceControl.Builder b = win.makeSurface()
+ mSurfaceControl = win.makeSurface()
.setParent(win.getSurfaceControl())
.setName(name)
.setFormat(format)
@@ -81,16 +80,8 @@
.setMetadata(METADATA_WINDOW_TYPE, windowType)
.setMetadata(METADATA_OWNER_UID, mWindowSession.mUid)
.setMetadata(METADATA_OWNER_PID, mWindowSession.mPid)
- .setCallsite("WindowSurfaceController");
-
- final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);
-
- if (useBLAST) {
- b.setBLASTLayer();
- }
-
- mSurfaceControl = b.build();
+ .setCallsite("WindowSurfaceController")
+ .setBLASTLayer().build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index 7edf445..ccd9bd0 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -20,6 +20,7 @@
#include <aidl/android/hardware/power/IPower.h>
#include <android-base/stringprintf.h>
+#include <inttypes.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
@@ -38,6 +39,15 @@
namespace android {
+static struct {
+ jclass clazz{};
+ jfieldID workPeriodStartTimestampNanos{};
+ jfieldID actualTotalDurationNanos{};
+ jfieldID actualCpuDurationNanos{};
+ jfieldID actualGpuDurationNanos{};
+ jfieldID timestampNanos{};
+} gWorkDurationInfo;
+
static power::PowerHalController gPowerHalController;
static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
static std::mutex gSessionMapLock;
@@ -180,6 +190,26 @@
setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
}
+static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
+ jobjectArray jWorkDurations) {
+ int size = env->GetArrayLength(jWorkDurations);
+ std::vector<WorkDuration> workDurations(size);
+ for (int i = 0; i < size; i++) {
+ jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
+ workDurations[i].workPeriodStartTimestampNanos =
+ env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos);
+ workDurations[i].durationNanos =
+ env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos);
+ workDurations[i].cpuDurationNanos =
+ env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos);
+ workDurations[i].gpuDurationNanos =
+ env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos);
+ workDurations[i].timeStampNanos =
+ env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos);
+ }
+ reportActualWorkDuration(session_ptr, workDurations);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sHintManagerServiceMethods[] = {
/* name, signature, funcPtr */
@@ -194,9 +224,23 @@
{"nativeSendHint", "(JI)V", (void*)nativeSendHint},
{"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
{"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
+ {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V",
+ (void*)nativeReportActualWorkDuration2},
};
int register_android_server_HintManagerService(JNIEnv* env) {
+ gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration");
+ gWorkDurationInfo.workPeriodStartTimestampNanos =
+ env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J");
+ gWorkDurationInfo.actualTotalDurationNanos =
+ env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J");
+ gWorkDurationInfo.actualCpuDurationNanos =
+ env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J");
+ gWorkDurationInfo.actualGpuDurationNanos =
+ env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J");
+ gWorkDurationInfo.timestampNanos =
+ env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J");
+
return jniRegisterNativeMethods(env,
"com/android/server/power/hint/"
"HintManagerService$NativeWrapper",
diff --git a/services/proguard.flags b/services/proguard.flags
index 407505d..88561b4 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -45,10 +45,6 @@
public static void write(...);
}
-# Binder interfaces
--keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface
--keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface
-
# Various classes subclassed in or referenced via JNI in ethernet-service
-keep public class android.net.** { *; }
-keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 6a95d5c..499e700 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -229,7 +229,19 @@
LIMIT_MODE_70.getPhysicalWidth(),
LIMIT_MODE_70.getPhysicalHeight(),
0, APP_MODE_65.getRefreshRate())),
- /*displayResolutionRangeVotingEnabled*/ true}});
+ /*displayResolutionRangeVotingEnabled*/ true},
+ {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+ /*expectedPhysicalRefreshRate*/ 64.99f,
+ /*expectedAppRequestedRefreshRate*/ 64.99f,
+ /*votesWithPriorities*/ Map.of(
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()),
+ Vote.PRIORITY_APP_REQUEST_SIZE,
+ Vote.forSize(APP_MODE_65.getPhysicalWidth(),
+ APP_MODE_65.getPhysicalHeight()),
+ Vote.PRIORITY_LOW_POWER_MODE,
+ Vote.forPhysicalRefreshRates(
+ 0, 64.99f))}});
final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 37fe8d1..f45dd39 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2625,7 +2625,7 @@
doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
doReturn(app).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
@@ -2637,7 +2637,7 @@
// Since sr.app is null, this service cannot be in the same process as the
// client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
app.mServices.updateHasAboveClientLocked();
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ updateOomAdj(app);
assertTrue(app.mServices.hasAboveClient());
assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
}
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index ef19ba1..e89199d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -39,6 +39,7 @@
crossProfileIntentResolutionStrategy='0'
mediaSharedWithParent='true'
credentialShareableWithParent='false'
+ authAlwaysRequiredToDisableQuietMode='true'
showInSettings='23'
hideInSettingsInQuietMode='true'
inheritDevicePolicy='450'
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
similarity index 92%
rename from services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
index 8608199..cfd0289 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java
@@ -37,11 +37,11 @@
import org.mockito.MockitoAnnotations;
/**
- * Tests for WindowMagnificationConnectionWrapper. We don't test {@code
- * WindowMagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in
+ * Tests for MagnificationConnectionWrapper. We don't test {@code
+ * MagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in
* {@link WindowMagnificationManagerTest}.
*/
-public class WindowMagnificationConnectionWrapperTest {
+public class MagnificationConnectionWrapperTest {
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
@@ -54,14 +54,14 @@
private MagnificationAnimationCallback mAnimationCallback;
private MockWindowMagnificationConnection mMockWindowMagnificationConnection;
- private WindowMagnificationConnectionWrapper mConnectionWrapper;
+ private MagnificationConnectionWrapper mConnectionWrapper;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mMockWindowMagnificationConnection = new MockWindowMagnificationConnection();
mConnection = mMockWindowMagnificationConnection.getConnection();
- mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection, mTrace);
+ mConnectionWrapper = new MagnificationConnectionWrapper(mConnection, mTrace);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
deleted file mode 100644
index 8f77e9b..0000000
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.virtual;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.Manifest;
-import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.camera.IVirtualCamera;
-import android.companion.virtual.camera.IVirtualCameraSession;
-import android.companion.virtual.camera.VirtualCamera;
-import android.companion.virtual.camera.VirtualCameraConfig;
-import android.companion.virtual.camera.VirtualCameraHalConfig;
-import android.companion.virtual.camera.VirtualCameraSession;
-import android.companion.virtual.camera.VirtualCameraStreamConfig;
-import android.companion.virtual.flags.Flags;
-import android.content.ComponentName;
-import android.graphics.ImageFormat;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableContext;
-import android.testing.TestableLooper;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.AdoptShellPermissionsRule;
-import com.android.server.companion.virtual.camera.IVirtualCameraService;
-import com.android.server.companion.virtual.camera.VirtualCameraController;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashSet;
-import java.util.Set;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class VirtualCameraTest {
-
- private static final String PKG = "com.android.virtualcamera";
- private static final String CLS = ".VirtualCameraService";
- public static final String CAMERA_DISPLAY_NAME = "testCamera";
-
- private final TestableContext mContext =
- new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
- private FakeVirtualCameraService mFakeVirtualCameraService;
- private VirtualCameraController mVirtualCameraController;
-
- @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext);
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Rule
- public AdoptShellPermissionsRule mAdoptShellPermissionsRule =
- new AdoptShellPermissionsRule(
- InstrumentationRegistry.getInstrumentation().getUiAutomation(),
- Manifest.permission.CREATE_VIRTUAL_DEVICE);
-
- @Before
- public void setUp() {
- mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController);
- mFakeVirtualCameraService = new FakeVirtualCameraService();
- connectFakeService();
- mVirtualCameraController = new VirtualCameraController(mContext);
- }
-
- private VirtualDeviceImpl createVirtualDevice() {
- return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
- }
-
- private void connectFakeService() {
- mContext.addMockService(
- ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder());
- }
-
- @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
- @Test
- public void addVirtualCamera() {
- VirtualDeviceImpl virtualDevice = createVirtualDevice();
- VirtualCameraConfig config = createVirtualCameraConfig(null);
- IVirtualCamera.Default camera = new IVirtualCamera.Default();
- virtualDevice.registerVirtualCamera(camera);
-
- assertThat(mFakeVirtualCameraService.mCameras).contains(camera);
- }
-
- @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
- @Test
- public void addVirtualCamera_serviceNotReady() {
- TestableContext context =
- new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
- VirtualCameraController virtualCameraController = new VirtualCameraController(context);
- mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController);
-
- VirtualDeviceImpl virtualDevice =
- mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build());
- IVirtualCamera.Default camera = new IVirtualCamera.Default();
- VirtualCameraConfig config = createVirtualCameraConfig(null);
- virtualDevice.registerVirtualCamera(camera);
- FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService();
-
- // Only add the service after connecting the camera
- virtualCameraController.onServiceConnected(
- ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder());
-
- assertThat(fakeVirtualCameraService.mCameras).contains(camera);
- }
-
- @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
- @Test
- public void getCameraConfiguration() {
- VirtualDeviceImpl virtualDevice = createVirtualDevice();
- VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
- VirtualCameraConfig config =
- new VirtualCameraConfig.Builder()
- .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
- .setDisplayName(CAMERA_DISPLAY_NAME)
- .setCallback(
- new HandlerExecutor(new Handler(Looper.getMainLooper())),
- () -> virtualCameraSession)
- .build();
-
- VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
-
- VirtualCameraConfig returnedConfig = virtualCamera.getConfig();
- assertThat(returnedConfig).isNotNull();
- assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME);
- Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs();
- assertThat(streamConfigs).hasSize(1);
- VirtualCameraStreamConfig streamConfig =
- streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0];
- assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565);
- assertThat(streamConfig.width).isEqualTo(10);
- assertThat(streamConfig.height).isEqualTo(10);
-
- VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig();
- assertThat(halConfig).isNotNull();
- assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME);
- assertThat(halConfig.streamConfigs).asList().hasSize(1);
- assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565);
- assertThat(halConfig.streamConfigs[0].width).isEqualTo(10);
- assertThat(halConfig.streamConfigs[0].height).isEqualTo(10);
- }
-
- @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA)
- @Test
- public void createCameraWithVirtualCameraInstance() {
- VirtualDeviceImpl virtualDevice = createVirtualDevice();
-
- VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {};
- VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession);
- VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config);
-
- assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera);
- assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class);
- }
-
- @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA)
- @Test
- public void createCameraDoesNothingWhenControllerIsNull() {
- mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null);
- VirtualDeviceImpl virtualDevice = createVirtualDevice();
- IVirtualCamera.Default camera = new IVirtualCamera.Default();
- VirtualCameraConfig config = createVirtualCameraConfig(null);
- virtualDevice.registerVirtualCamera(camera);
-
- assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera);
- }
-
- @NonNull
- private static VirtualCameraConfig createVirtualCameraConfig(
- VirtualCameraSession virtualCameraSession) {
- return new VirtualCameraConfig.Builder()
- .addStreamConfiguration(10, 10, ImageFormat.RGB_565)
- .setDisplayName(CAMERA_DISPLAY_NAME)
- .setCallback(
- new HandlerExecutor(new Handler(Looper.getMainLooper())),
- () -> virtualCameraSession)
- .build();
- }
-
- private static class FakeVirtualCameraService extends IVirtualCameraService.Stub {
-
- final Set<IVirtualCamera> mCameras = new HashSet<>();
-
- @Override
- public boolean registerCamera(IVirtualCamera camera) throws RemoteException {
- mCameras.add(camera);
- return true;
- }
-
- @Override
- public void unregisterCamera(IVirtualCamera camera) throws RemoteException {
- mCameras.remove(camera);
- }
- }
-}
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 f978990..30300ec 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
@@ -1940,7 +1940,7 @@
mRunningAppsChangedCallback,
params,
new DisplayManagerGlobal(mIDisplayManager),
- new VirtualCameraController(mContext));
+ new VirtualCameraController());
mVdms.addVirtualDevice(virtualDeviceImpl);
assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId());
assertThat(virtualDeviceImpl.getPersistentDeviceId())
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
deleted file mode 100644
index dbd6c88..0000000
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.virtual;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyFloat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-
-import android.app.admin.DevicePolicyManager;
-import android.companion.AssociationInfo;
-import android.companion.virtual.IVirtualDeviceActivityListener;
-import android.companion.virtual.IVirtualDeviceSoundEffectListener;
-import android.companion.virtual.VirtualDeviceParams;
-import android.companion.virtual.flags.Flags;
-import android.content.AttributionSource;
-import android.content.Context;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.IDisplayManager;
-import android.net.MacAddress;
-import android.os.Binder;
-import android.testing.TestableContext;
-import android.util.ArraySet;
-import android.view.Display;
-import android.view.DisplayInfo;
-import android.view.WindowManager;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.LocalServices;
-import com.android.server.companion.virtual.camera.VirtualCameraController;
-import com.android.server.input.InputManagerInternal;
-import com.android.server.sensors.SensorManagerInternal;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Objects;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-/** Test rule to generate instances of {@link VirtualDeviceImpl}. */
-public class VirtualDeviceRule implements TestRule {
-
- private static final int DEVICE_OWNER_UID = 50;
- private static final int VIRTUAL_DEVICE_ID = 42;
-
- private final Context mContext;
- private InputController mInputController;
- private CameraAccessController mCameraAccessController;
- private AssociationInfo mAssociationInfo;
- private VirtualDeviceManagerService mVdms;
- private VirtualDeviceManagerInternal mLocalService;
- private VirtualDeviceLog mVirtualDeviceLog;
-
- // Mocks
- @Mock private InputController.NativeWrapper mNativeWrapperMock;
- @Mock private DisplayManagerInternal mDisplayManagerInternalMock;
- @Mock private IDisplayManager mIDisplayManager;
- @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback;
- @Mock private DevicePolicyManager mDevicePolicyManagerMock;
- @Mock private InputManagerInternal mInputManagerInternalMock;
- @Mock private SensorManagerInternal mSensorManagerInternalMock;
- @Mock private IVirtualDeviceActivityListener mActivityListener;
- @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener;
- @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
- @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback;
-
- // Test instance suppliers
- private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier;
-
- /**
- * Create a new {@link VirtualDeviceRule}
- *
- * @param context The context to be used with the rule.
- */
- public VirtualDeviceRule(@NonNull Context context) {
- Objects.requireNonNull(context);
- mContext = context;
- }
-
- /**
- * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the
- * supplier returns null, a new instance will be created.
- */
- public VirtualDeviceRule withVirtualCameraControllerSupplier(
- Supplier<VirtualCameraController> virtualCameraControllerSupplier) {
- mVirtualCameraControllerSupplier = virtualCameraControllerSupplier;
- return this;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- init(new TestableContext(mContext));
- base.evaluate();
- }
- };
- }
-
- private void init(@NonNull TestableContext context) {
- MockitoAnnotations.initMocks(this);
-
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
- doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
- doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
- doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
- LocalServices.removeServiceForTest(InputManagerInternal.class);
- LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
-
- LocalServices.removeServiceForTest(SensorManagerInternal.class);
- LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
-
- final DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.uniqueId = "uniqueId";
- doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
- doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock)
- .getDisplayIdToMirror(anyInt());
- LocalServices.removeServiceForTest(DisplayManagerInternal.class);
- LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
-
- context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock);
-
- // Allow virtual devices to be created on the looper thread for testing.
- final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
- mInputController =
- new InputController(
- mNativeWrapperMock,
- InstrumentationRegistry.getInstrumentation()
- .getContext()
- .getMainThreadHandler(),
- context.getSystemService(WindowManager.class),
- threadVerifier);
- mCameraAccessController =
- new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback);
-
- mAssociationInfo =
- new AssociationInfo(
- /* associationId= */ 1,
- 0,
- null,
- null,
- MacAddress.BROADCAST_ADDRESS,
- "",
- null,
- null,
- true,
- false,
- false,
- 0,
- 0,
- -1);
-
- mVdms = new VirtualDeviceManagerService(context);
- mLocalService = mVdms.getLocalServiceInstance();
- mVirtualDeviceLog = new VirtualDeviceLog(context);
- }
-
- /**
- * Create a {@link VirtualDeviceImpl} with the required mocks
- *
- * @param params See {@link
- * android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int,
- * VirtualDeviceParams)}
- */
- public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) {
- VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get();
- if (Flags.virtualCamera()) {
- if (virtualCameraController == null) {
- virtualCameraController = new VirtualCameraController(mContext);
- }
- }
-
- VirtualDeviceImpl virtualDeviceImpl =
- new VirtualDeviceImpl(
- mContext,
- mAssociationInfo,
- mVdms,
- mVirtualDeviceLog,
- new Binder(),
- new AttributionSource(
- DEVICE_OWNER_UID,
- "com.android.virtualdevice.test",
- "virtualdevicerule"),
- VIRTUAL_DEVICE_ID,
- mInputController,
- mCameraAccessController,
- mPendingTrampolineCallback,
- mActivityListener,
- mSoundEffectListener,
- mRunningAppsChangedCallback,
- params,
- new DisplayManagerGlobal(mIDisplayManager),
- virtualCameraController);
- mVdms.addVirtualDevice(virtualDeviceImpl);
- return virtualDeviceImpl;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
new file mode 100644
index 0000000..2583023
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.virtual.camera;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.companion.virtual.camera.VirtualCameraCallback;
+import android.companion.virtual.camera.VirtualCameraConfig;
+import android.companion.virtual.camera.VirtualCameraMetadata;
+import android.companion.virtual.camera.VirtualCameraStreamConfig;
+import android.companion.virtualcamera.IVirtualCameraService;
+import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.graphics.ImageFormat;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class VirtualCameraControllerTest {
+
+ private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10;
+ private static final int CAMERA_WIDTH_1 = 100;
+ private static final int CAMERA_HEIGHT_1 = 200;
+ private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565;
+
+ private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11;
+ private static final int CAMERA_WIDTH_2 = 400;
+ private static final int CAMERA_HEIGHT_2 = 600;
+ private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2;
+
+ @Mock
+ private IVirtualCameraService mVirtualCameraServiceMock;
+
+ private VirtualCameraController mVirtualCameraController;
+ private final HandlerExecutor mCallbackHandler =
+ new HandlerExecutor(new Handler(Looper.getMainLooper()));
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock);
+ when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true);
+ }
+
+ @Test
+ public void registerCamera_registersCamera() throws Exception {
+ mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+
+ ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
+ ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
+ verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture());
+ VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue();
+ assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1);
+ assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1,
+ CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+ }
+
+ @Test
+ public void unregisterCamera_unregistersCamera() throws Exception {
+ VirtualCameraConfig config = createVirtualCameraConfig(
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1);
+ mVirtualCameraController.unregisterCamera(config);
+
+ verify(mVirtualCameraServiceMock).unregisterCamera(any());
+ }
+
+ @Test
+ public void close_unregistersAllCameras() throws Exception {
+ mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1));
+ mVirtualCameraController.registerCamera(createVirtualCameraConfig(
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2));
+
+ ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
+ ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
+ mVirtualCameraController.close();
+ verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(),
+ configurationCaptor.capture());
+ List<VirtualCameraConfiguration> virtualCameraConfigurations =
+ configurationCaptor.getAllValues();
+ assertThat(virtualCameraConfigurations).hasSize(2);
+ assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1,
+ CAMERA_HEIGHT_1, CAMERA_FORMAT_1);
+ assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2,
+ CAMERA_HEIGHT_2, CAMERA_FORMAT_2);
+ }
+
+ private VirtualCameraConfig createVirtualCameraConfig(
+ int width, int height, int format, int displayNameResId) {
+ return new VirtualCameraConfig.Builder()
+ .addStreamConfig(width, height, format)
+ .setDisplayNameStringRes(displayNameResId)
+ .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback())
+ .build();
+ }
+
+ private static void assertVirtualCameraConfiguration(
+ VirtualCameraConfiguration configuration, int width, int height, int format) {
+ assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width);
+ assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height);
+ assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format);
+ }
+
+ private static VirtualCameraCallback createNoOpCallback() {
+ return new VirtualCameraCallback() {
+
+ @Override
+ public void onStreamConfigured(
+ int streamId,
+ @NonNull Surface surface,
+ @NonNull VirtualCameraStreamConfig streamConfig) {}
+
+ @Override
+ public void onProcessCaptureRequest(
+ int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {}
+
+ @Override
+ public void onStreamClosed(int streamId) {}
+ };
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
index 2cdfbff..13dc120 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java
@@ -57,7 +57,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.IBatteryStats;
-import com.android.net.flags.Flags;
+import com.android.modules.utils.build.SdkLevel;
import org.junit.After;
import org.junit.Before;
@@ -264,7 +264,7 @@
verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
mNMService.setDataSaverModeEnabled(true);
- if (Flags.setDataSaverViaCm()) {
+ if (SdkLevel.isAtLeastV()) {
verify(mCm).setDataSaverEnabled(true);
} else {
verify(mNetdService).bandwidthEnableDataSaver(true);
@@ -284,7 +284,7 @@
mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
mNMService.setDataSaverModeEnabled(false);
- if (Flags.setDataSaverViaCm()) {
+ if (SdkLevel.isAtLeastV()) {
verify(mCm).setDataSaverEnabled(false);
} else {
verify(mNetdService).bandwidthEnableDataSaver(false);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index c684a7b..57b1225 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -67,6 +67,7 @@
.setCrossProfileIntentResolutionStrategy(0)
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setDeleteAppWithParent(false)
.setAlwaysVisible(false)
.build();
@@ -80,6 +81,7 @@
actualProps.setCrossProfileIntentResolutionStrategy(1);
actualProps.setMediaSharedWithParent(true);
actualProps.setCredentialShareableWithParent(false);
+ actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
actualProps.setDeleteAppWithParent(true);
actualProps.setAlwaysVisible(true);
@@ -123,6 +125,7 @@
.setInheritDevicePolicy(1732)
.setMediaSharedWithParent(true)
.setDeleteAppWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setAlwaysVisible(true)
.build();
final UserProperties orig = new UserProperties(defaultProps);
@@ -131,6 +134,7 @@
orig.setShowInSettings(1437);
orig.setInheritDevicePolicy(9456);
orig.setDeleteAppWithParent(false);
+ orig.setAuthAlwaysRequiredToDisableQuietMode(true);
orig.setAlwaysVisible(false);
// Test every permission level. (Currently, it's linear so it's easy.)
@@ -182,6 +186,8 @@
hasManagePermission);
assertEqualGetterOrThrows(orig::getUseParentsContacts,
copy::getUseParentsContacts, hasManagePermission);
+ assertEqualGetterOrThrows(orig::isAuthAlwaysRequiredToDisableQuietMode,
+ copy::isAuthAlwaysRequiredToDisableQuietMode, hasManagePermission);
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
@@ -242,6 +248,8 @@
.isEqualTo(actual.isMediaSharedWithParent());
assertThat(expected.isCredentialShareableWithParent())
.isEqualTo(actual.isCredentialShareableWithParent());
+ assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
+ .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 20270a8..48eb5c6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -89,6 +89,7 @@
.setCrossProfileIntentResolutionStrategy(1)
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(false)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
.setShowInSettings(900)
.setHideInSettingsInQuietMode(true)
.setInheritDevicePolicy(340)
@@ -160,6 +161,8 @@
.getCrossProfileIntentResolutionStrategy());
assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
+ assertTrue(type.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(340, type.getDefaultUserPropertiesReference()
@@ -306,6 +309,7 @@
.setCrossProfileIntentResolutionStrategy(1)
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setShowInSettings(20)
.setHideInSettingsInQuietMode(false)
.setInheritDevicePolicy(21)
@@ -347,6 +351,8 @@
assertFalse(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertTrue(aospType.getDefaultUserPropertiesReference()
.isCredentialShareableWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(21, aospType.getDefaultUserPropertiesReference()
@@ -394,6 +400,8 @@
assertTrue(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertFalse(aospType.getDefaultUserPropertiesReference()
.isCredentialShareableWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(450, aospType.getDefaultUserPropertiesReference()
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 775d42a..2b6d8ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -331,6 +331,9 @@
.isEqualTo(privateProfileUserProperties.isMediaSharedWithParent());
assertThat(typeProps.isCredentialShareableWithParent())
.isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent());
+ assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode())
+ .isEqualTo(privateProfileUserProperties
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
// Verify private profile parent
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index d09aa89..3748527 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -44,6 +44,7 @@
import android.os.IHintSession;
import android.os.PerformanceHintManager;
import android.os.Process;
+import android.os.WorkDuration;
import android.util.Log;
import com.android.server.FgThread;
@@ -89,6 +90,11 @@
private static final long[] DURATIONS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
+ private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] {
+ new WorkDuration(1L, 11L, 8L, 4L, 1L),
+ new WorkDuration(2L, 13L, 8L, 6L, 2L),
+ new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L),
+ };
@Mock private Context mContext;
@Mock private HintManagerService.NativeWrapper mNativeWrapperMock;
@@ -593,4 +599,55 @@
}
a.close();
}
+
+ @Test
+ public void testReportActualWorkDuration2() throws Exception {
+ HintManagerService service = createService();
+ IBinder token = new Binder();
+
+ AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+ .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+ a.updateTargetWorkDuration(100L);
+ a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
+ eq(WORK_DURATIONS_THREE));
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.reportActualWorkDuration2(new WorkDuration[] {});
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)});
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)});
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)});
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ a.reportActualWorkDuration2(
+ new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)});
+ });
+
+ reset(mNativeWrapperMock);
+ // Set session to background, then the duration would not be updated.
+ service.mUidObserver.onUidStateChanged(
+ a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+
+ // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
+ final CountDownLatch latch = new CountDownLatch(1);
+ FgThread.getHandler().post(() -> {
+ latch.countDown();
+ });
+ latch.await();
+
+ assertFalse(service.mUidObserver.isUidForeground(a.mUid));
+ a.reportActualWorkDuration2(WORK_DURATIONS_THREE);
+ verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
new file mode 100644
index 0000000..8dcf89b
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.service.notification.ZenDeviceEffects;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ZenDeviceEffectsTest extends UiServiceTestCase {
+
+ @Test
+ public void builder() {
+ ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false)
+ .setShouldDisableTiltToWake(true)
+ .setShouldMaximizeDoze(true)
+ .setShouldUseNightMode(false)
+ .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
+ .build();
+
+ assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
+ assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse();
+ assertThat(deviceEffects.shouldDisableTapToWake()).isFalse();
+ assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue();
+ assertThat(deviceEffects.shouldDisableTouch()).isFalse();
+ assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse();
+ assertThat(deviceEffects.shouldMaximizeDoze()).isTrue();
+ assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
+ assertThat(deviceEffects.shouldUseNightMode()).isFalse();
+ assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
+ }
+
+ @Test
+ public void builder_fromInstance() {
+ ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisableTiltToWake(true)
+ .setShouldUseNightMode(true)
+ .setShouldSuppressAmbientDisplay(true)
+ .build();
+
+ ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(false)
+ .build();
+
+ assertThat(modified.shouldDimWallpaper()).isTrue(); // from original
+ assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original
+ assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated
+ assertThat(modified.shouldUseNightMode()).isFalse(); // updated
+ assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original
+ }
+
+ @Test
+ public void writeToParcel_parcelsAndUnparcels() {
+ ZenDeviceEffects source = new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .setShouldDisableTouch(true)
+ .setShouldMinimizeRadioUsage(true)
+ .setShouldUseNightMode(true)
+ .setShouldSuppressAmbientDisplay(true)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ ZenDeviceEffects copy;
+ try {
+ source.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+
+ assertThat(copy.shouldDimWallpaper()).isTrue();
+ assertThat(copy.shouldDisableTouch()).isTrue();
+ assertThat(copy.shouldMinimizeRadioUsage()).isTrue();
+ assertThat(copy.shouldUseNightMode()).isTrue();
+ assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
+ assertThat(copy.shouldDisplayGrayscale()).isFalse();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index eab8757..912e1d3d 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -16,15 +16,19 @@
package com.android.server.policy;
+import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS;
import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS;
import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY;
+import android.app.ActivityManager.RecentTaskInfo;
import android.content.ComponentName;
+import android.os.RemoteException;
import android.provider.Settings;
import org.junit.Test;
@@ -120,6 +124,46 @@
mPhoneWindowManager.assertStatusBarStartAssist();
}
+ @Test
+ public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
+ throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
+ int referenceId = 666;
+ recentTaskInfo.persistentId = referenceId;
+ doReturn(recentTaskInfo).when(
+ mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground();
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertOpenAllAppView();
+ mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ }
+
+ @Test
+ public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException {
+ overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+ mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+ mPhoneWindowManager.overrideIsUserSetupComplete(true);
+ RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
+ int referenceId = 666;
+ recentTaskInfo.persistentId = referenceId;
+ doReturn(recentTaskInfo).when(
+ mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground();
+
+ sendKey(KEYCODE_STEM_PRIMARY);
+ sendKey(KEYCODE_STEM_PRIMARY);
+
+ mPhoneWindowManager.assertNotOpenAllAppView();
+ mPhoneWindowManager.assertSwitchToRecent(referenceId);
+ }
private void overrideBehavior(String key, int expectedBehavior) {
Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index e26260a..314cd04 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -57,6 +57,7 @@
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.SearchManager;
import android.content.ComponentName;
@@ -126,7 +127,8 @@
@Mock private WindowManagerInternal mWindowManagerInternal;
@Mock private ActivityManagerInternal mActivityManagerInternal;
- @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock IActivityManager mActivityManagerService;
@Mock private InputManagerInternal mInputManagerInternal;
@Mock private InputManager mInputManager;
@Mock private SensorPrivacyManager mSensorPrivacyManager;
@@ -181,6 +183,10 @@
KeyguardServiceDelegate getKeyguardServiceDelegate() {
return mKeyguardServiceDelegate;
}
+
+ IActivityManager getActivityManagerService() {
+ return mActivityManagerService;
+ }
}
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -347,6 +353,10 @@
mPhoneWindowManager.mShortPressOnPowerBehavior = behavior;
}
+ void overrideShouldEarlyShortPressOnStemPrimary(boolean shouldEarlyShortPress) {
+ mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress;
+ }
+
// Override assist perform function.
void overrideLongPressOnPower(int behavior) {
mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -667,4 +677,11 @@
vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
expectedModifierState), description(errorMsg));
}
+
+ void assertSwitchToRecent(int persistentId) throws RemoteException {
+ mTestLooper.dispatchAll();
+ verify(mActivityManagerService,
+ timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId),
+ isNull());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 0c996e0..1776ba5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -195,6 +195,8 @@
setBooted(mAtm);
// Because the booted state is set, avoid starting real home if there is no task.
doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any());
+ // Do not execute the transaction, because we can't verify the parameter after it recycles.
+ doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
@@ -262,7 +264,7 @@
pauseFound.value = true;
}
return null;
- }).when(activity.app.getThread()).scheduleTransaction(any());
+ }).when(mClientLifecycleManager).scheduleTransaction(any());
activity.setState(STOPPED, "testPausingWhenVisibleFromStopped");
@@ -477,7 +479,7 @@
.build();
final Task task = activity.getTask();
activity.setState(DESTROYED, "Testing");
- clearInvocations(mAtm.getLifecycleManager());
+ clearInvocations(mClientLifecycleManager);
final Configuration newConfig = new Configuration(task.getConfiguration());
newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT
@@ -487,7 +489,7 @@
ensureActivityConfiguration(activity);
- verify(mAtm.getLifecycleManager(), never())
+ verify(mClientLifecycleManager, never())
.scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class));
}
@@ -500,7 +502,7 @@
// test properly.
activity.finishRelaunching();
// Clear out any calls to scheduleTransaction from launching the activity.
- reset(mAtm.getLifecycleManager());
+ reset(mClientLifecycleManager);
final Task task = activity.getTask();
activity.setState(RESUMED, "Testing");
@@ -517,7 +519,7 @@
// The configuration change is still sent to the activity, even if it doesn't relaunch.
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
- verify(mAtm.getLifecycleManager()).scheduleTransaction(
+ verify(mClientLifecycleManager).scheduleTransaction(
eq(activity.app.getThread()), eq(expected));
}
@@ -558,19 +560,7 @@
activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
activity.getConfiguration()));
- clearInvocations(mAtm.getLifecycleManager());
- final Configuration newConfig = new Configuration(activity.getConfiguration());
- final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
- final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
- if (newConfig.orientation == ORIENTATION_PORTRAIT) {
- newConfig.orientation = ORIENTATION_LANDSCAPE;
- newConfig.screenWidthDp = longSide;
- newConfig.screenHeightDp = shortSide;
- } else {
- newConfig.orientation = ORIENTATION_PORTRAIT;
- newConfig.screenWidthDp = shortSide;
- newConfig.screenHeightDp = longSide;
- }
+ clearInvocations(mClientLifecycleManager);
// Mimic the behavior that display doesn't handle app's requested orientation.
final DisplayContent dc = activity.getTask().getDisplayContent();
@@ -578,12 +568,15 @@
doReturn(false).when(dc).handlesOrientationChangeFromDescendant(anyInt());
final int requestedOrientation;
- switch (newConfig.orientation) {
- case ORIENTATION_LANDSCAPE:
- requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE;
- break;
+ final int expectedOrientation;
+ switch (activity.getConfiguration().orientation) {
case ORIENTATION_PORTRAIT:
+ requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ expectedOrientation = ORIENTATION_LANDSCAPE;
+ break;
+ case ORIENTATION_LANDSCAPE:
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+ expectedOrientation = ORIENTATION_PORTRAIT;
break;
default:
throw new IllegalStateException("Orientation in new config should be either"
@@ -595,11 +588,11 @@
activity.setRequestedOrientation(requestedOrientation);
+ final Configuration currentConfig = activity.getConfiguration();
+ assertEquals(expectedOrientation, currentConfig.orientation);
final ActivityConfigurationChangeItem expected =
- ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
- verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
- eq(expected));
-
+ ActivityConfigurationChangeItem.obtain(activity.token, currentConfig);
+ verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
verify(displayRotation).onSetRequestedOrientation();
}
@@ -788,7 +781,7 @@
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
try {
- clearInvocations(mAtm.getLifecycleManager());
+ clearInvocations(mClientLifecycleManager);
doReturn(false).when(stack).isTranslucent(any());
assertTrue(task.shouldBeVisible(null /* starting */));
@@ -796,7 +789,10 @@
activity.getConfiguration()));
final Configuration newConfig = new Configuration(activity.getConfiguration());
- final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
+ final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp
+ // To avoid the case where it is always portrait because of width == height.
+ ? newConfig.screenWidthDp - 1
+ : Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp);
final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp);
if (newConfig.orientation == ORIENTATION_PORTRAIT) {
newConfig.orientation = ORIENTATION_LANDSCAPE;
@@ -811,12 +807,12 @@
task.onConfigurationChanged(newConfig);
activity.ensureActivityConfiguration(0 /* globalChanges */,
- false /* preserveWindow */, true /* ignoreStopState */);
+ false /* preserveWindow */, true /* ignoreVisibility */);
final ActivityConfigurationChangeItem expected =
- ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
- verify(mAtm.getLifecycleManager()).scheduleTransaction(
- eq(activity.app.getThread()), eq(expected));
+ ActivityConfigurationChangeItem.obtain(activity.token,
+ activity.getConfiguration());
+ verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected);
} finally {
stack.getDisplayArea().removeChild(stack);
}
@@ -1259,12 +1255,12 @@
targetActivity.resultTo = sourceActivity;
targetActivity.setForceSendResultForMediaProjection();
- clearInvocations(mAtm.getLifecycleManager());
+ clearInvocations(mClientLifecycleManager);
targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
try {
- verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
+ verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction(
any(ClientTransaction.class));
} catch (RemoteException ignored) {
}
@@ -1283,7 +1279,7 @@
targetActivity.setState(RESUMED, "test");
targetActivity.resultTo = resultToActivity;
- clearInvocations(mAtm.getLifecycleManager());
+ clearInvocations(mClientLifecycleManager);
targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
waitUntilHandlersIdle();
@@ -1786,10 +1782,10 @@
final ActivityRecord activity = createActivityWithTask();
final WindowProcessController wpc = activity.app;
setup.accept(activity);
- clearInvocations(mAtm.getLifecycleManager());
+ clearInvocations(mClientLifecycleManager);
activity.getTask().removeImmediately("test");
try {
- verify(mAtm.getLifecycleManager()).scheduleTransaction(any(),
+ verify(mClientLifecycleManager).scheduleTransaction(any(),
isA(DestroyActivityItem.class));
} catch (RemoteException ignored) {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index e7ebd7db..3c027ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -39,9 +39,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.never;
@@ -51,7 +52,7 @@
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
-import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.EnterPipRequestedItem;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -90,9 +91,6 @@
@RunWith(WindowTestRunner.class)
public class ActivityTaskManagerServiceTests extends WindowTestsBase {
- private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor =
- ArgumentCaptor.forClass(ClientTransaction.class);
-
private static final String DEFAULT_PACKAGE_NAME = "my.application.package";
private static final int DEFAULT_USER_ID = 100;
@@ -123,53 +121,42 @@
final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class);
doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager();
doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+ clearInvocations(mClientLifecycleManager);
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
- verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture());
- final ClientTransaction transaction = mClientTransactionCaptor.getValue();
+ final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor =
+ ArgumentCaptor.forClass(ClientTransactionItem.class);
+ verify(mockLifecycleManager).scheduleTransaction(any(),
+ clientTransactionItemCaptor.capture());
+ final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue();
// Check that only an enter pip request item callback was scheduled.
- assertEquals(1, transaction.getCallbacks().size());
- assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem);
- // Check the activity lifecycle state remains unchanged.
- assertNull(transaction.getLifecycleStateRequest());
+ assertTrue(transactionItem instanceof EnterPipRequestedItem);
}
@Test
public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
- ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
doReturn(false).when(activity).inPinnedWindowingMode();
doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean());
+ clearInvocations(mClientLifecycleManager);
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
- verify(lifecycleManager, atLeast(0))
- .scheduleTransaction(mClientTransactionCaptor.capture());
- final ClientTransaction transaction = mClientTransactionCaptor.getValue();
- // Check that none are enter pip request items.
- transaction.getCallbacks().forEach(clientTransactionItem -> {
- assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
- });
+ verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
}
@Test
public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
- ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
doReturn(true).when(activity).inPinnedWindowingMode();
+ clearInvocations(mClientLifecycleManager);
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
- verify(lifecycleManager, atLeast(0))
- .scheduleTransaction(mClientTransactionCaptor.capture());
- final ClientTransaction transaction = mClientTransactionCaptor.getValue();
- // Check that none are enter pip request items.
- transaction.getCallbacks().forEach(clientTransactionItem -> {
- assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
- });
+ verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 6790dc2..afea811 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -117,6 +117,20 @@
}
@Test
+ public void noBackWhenMoveTaskToBack() {
+ Task taskA = createTask(mDefaultDisplay);
+ ActivityRecord recordA = createActivityRecord(taskA);
+ Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
+ final Task topTask = createTopTaskWithActivity();
+ withSystemCallback(topTask);
+ // simulate moveTaskToBack
+ topTask.setVisibleRequested(false);
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNull();
+ }
+
+ @Test
public void backTypeCrossTaskWhenBackToPreviousTask() {
Task taskA = createTask(mDefaultDisplay);
ActivityRecord recordA = createActivityRecord(taskA);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 491d5b5..8de45b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -202,8 +202,7 @@
any() /* starting */, anyInt() /* configChanges */,
anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */);
doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt());
- ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager();
- doNothing().when(lifecycleManager).scheduleTransaction(any());
+ doNothing().when(mClientLifecycleManager).scheduleTransaction(any());
startRecentsActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index a83caa4..dade3b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -636,6 +636,7 @@
transition.collect(app);
controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
null /* displayChange */);
+ assertTrue(delegateProc.isRunningRemoteTransition());
testPlayer.startTransition();
app.onStartingWindowDrawn();
// The task appeared event should be deferred until transition ready.
@@ -643,7 +644,6 @@
testPlayer.onTransactionReady(app.getSyncTransaction());
assertTrue(task.taskAppearedReady());
assertTrue(playerProc.isRunningRemoteTransition());
- assertTrue(delegateProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
assertTrue(app.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index eaeb804..d08ab51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -859,9 +859,7 @@
final int callingUid = Process.FIRST_APPLICATION_UID;
final int callingPid = 1234;
final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IWindow window = mock(IWindow.class);
- final IBinder windowToken = mock(IBinder.class);
- when(window.asBinder()).thenReturn(windowToken);
+ final IBinder window = new Binder();
final IBinder focusGrantToken = mock(IBinder.class);
final InputChannel inputChannel = new InputChannel();
@@ -879,9 +877,7 @@
final int callingUid = Process.SYSTEM_UID;
final int callingPid = 1234;
final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IWindow window = mock(IWindow.class);
- final IBinder windowToken = mock(IBinder.class);
- when(window.asBinder()).thenReturn(windowToken);
+ final IBinder window = new Binder();
final IBinder focusGrantToken = mock(IBinder.class);
final InputChannel inputChannel = new InputChannel();
@@ -901,9 +897,7 @@
final int callingUid = Process.FIRST_APPLICATION_UID;
final int callingPid = 1234;
final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IWindow window = mock(IWindow.class);
- final IBinder windowToken = mock(IBinder.class);
- when(window.asBinder()).thenReturn(windowToken);
+ final IBinder window = new Binder();
final IBinder focusGrantToken = mock(IBinder.class);
final InputChannel inputChannel = new InputChannel();
@@ -927,9 +921,7 @@
final int callingUid = Process.SYSTEM_UID;
final int callingPid = 1234;
final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IWindow window = mock(IWindow.class);
- final IBinder windowToken = mock(IBinder.class);
- when(window.asBinder()).thenReturn(windowToken);
+ final IBinder window = new Binder();
final IBinder focusGrantToken = mock(IBinder.class);
final InputChannel inputChannel = new InputChannel();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 1494f94..28e0c6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1337,8 +1337,8 @@
// Since we have a window we have to wait for it to draw to finish sync.
verify(mockCallback, never()).onTransactionReady(anyInt(), any());
- assertTrue(w1.useBLASTSync());
- assertTrue(w2.useBLASTSync());
+ assertTrue(w1.syncNextBuffer());
+ assertTrue(w2.syncNextBuffer());
// Make second (bottom) ready. If we started with the top, since activities fillsParent
// by default, the sync would be considered finished.
@@ -1348,16 +1348,16 @@
assertEquals(SYNC_STATE_READY, w2.mSyncState);
// Even though one Window finished drawing, both windows should still be using blast sync
- assertTrue(w1.useBLASTSync());
- assertTrue(w2.useBLASTSync());
+ assertTrue(w1.syncNextBuffer());
+ assertTrue(w2.syncNextBuffer());
// A drawn window can complete the sync state automatically.
w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
makeLastConfigReportedToClient(w1, true /* visible */);
mWm.mSyncEngine.onSurfacePlacement();
verify(mockCallback).onTransactionReady(anyInt(), any());
- assertFalse(w1.useBLASTSync());
- assertFalse(w2.useBLASTSync());
+ assertFalse(w1.syncNextBuffer());
+ assertFalse(w2.syncNextBuffer());
}
@Test
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 b89182d..46cff8b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,29 +306,28 @@
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- final ClientLifecycleManager clientManager = mAtm.getLifecycleManager();
- doNothing().when(clientManager).scheduleTransaction(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
// Non-cached state will send the change directly.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
- clearInvocations(clientManager);
+ clearInvocations(mClientLifecycleManager);
mWpc.onConfigurationChanged(newConfig);
- verify(clientManager).scheduleTransaction(eq(thread), any());
+ verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any());
// Cached state won't send the change.
- clearInvocations(clientManager);
+ clearInvocations(mClientLifecycleManager);
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
- verify(clientManager, never()).scheduleTransaction(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransaction(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(clientManager).scheduleTransaction(eq(thread), captor.capture());
+ verify(mClientLifecycleManager).scheduleTransaction(eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
final ArgumentCaptor<Configuration> configCaptor =
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 014d57d..67384b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -551,7 +551,7 @@
final SurfaceControl.Transaction[] handledT = { null };
// The normal case that the draw transaction is applied with finishing drawing.
win.applyWithNextDraw(t -> handledT[0] = t);
- assertTrue(win.useBLASTSync());
+ assertTrue(win.syncNextBuffer());
final SurfaceControl.Transaction drawT = new StubTransaction();
final SurfaceControl.Transaction currT = win.getSyncTransaction();
clearInvocations(currT);
@@ -560,12 +560,12 @@
// The draw transaction should be merged to current transaction even if the state is hidden.
verify(currT).merge(eq(drawT));
assertEquals(drawT, handledT[0]);
- assertFalse(win.useBLASTSync());
+ assertFalse(win.syncNextBuffer());
// If the window is gone before reporting drawn, the sync state should be cleared.
win.applyWithNextDraw(t -> handledT[0] = t);
win.destroySurfaceUnchecked();
- assertFalse(win.useBLASTSync());
+ assertFalse(win.syncNextBuffer());
assertNotEquals(drawT, handledT[0]);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index e0ed642..df4af11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -152,6 +152,7 @@
ActivityTaskManagerService mAtm;
RootWindowContainer mRootWindowContainer;
ActivityTaskSupervisor mSupervisor;
+ ClientLifecycleManager mClientLifecycleManager;
WindowManagerService mWm;
private final IWindow mIWindow = new TestIWindow();
private Session mTestSession;
@@ -215,6 +216,7 @@
mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
mSupervisor = mAtm.mTaskSupervisor;
mRootWindowContainer = mAtm.mRootWindowContainer;
+ mClientLifecycleManager = mAtm.getLifecycleManager();
mWm = mSystemServicesTestRule.getWindowManagerService();
mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled;
SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index b1a7d81..8c6e101 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -83,6 +85,7 @@
/** @hide */
@IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Direction {
}
/**
@@ -96,6 +99,7 @@
/** @hide */
@IntDef(value = {AUDIO_CALL, VIDEO_CALL})
+ @Retention(RetentionPolicy.SOURCE)
public @interface CallType {
}
/**
@@ -110,6 +114,7 @@
/** @hide */
@IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true)
+ @Retention(RetentionPolicy.SOURCE)
public @interface CallCapability {
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index baeff06..c124079 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -8875,6 +8875,19 @@
KEY_PREFIX + "child_session_aes_ctr_key_size_int_array";
/**
+ * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode
+ * of child session.
+ * Possible values are:
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+ public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY =
+ KEY_PREFIX + "child_session_aes_gcm_key_size_int_array";
+
+ /**
* List of supported encryption algorithms for child session. Possible values are
* {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
* {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
@@ -8883,6 +8896,16 @@
KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array";
/**
+ * List of supported AEAD algorithms for child session. Possible values are
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8},
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12},
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+ public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY =
+ KEY_PREFIX + "supported_child_session_aead_algorithms_int_array";
+
+ /**
* Time in seconds after which the IKE session is terminated if rekey procedure is not
* successful. If not set or set to <= 0, default value is 3600 seconds.
*/
@@ -8919,6 +8942,19 @@
KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array";
/**
+ * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode
+ * of IKE session.
+ * Possible values -
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192},
+ * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+ public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY =
+ KEY_PREFIX + "ike_session_encryption_aes_gcm_key_size_int_array";
+
+ /**
* List of supported encryption algorithms for IKE session. Possible values are
* {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC},
* {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR}
@@ -8927,6 +8963,16 @@
KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array";
/**
+ * List of supported AEAD algorithms for IKE session. Possible values are
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8},
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12},
+ * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16}
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS)
+ public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY =
+ KEY_PREFIX + "supported_ike_session_aead_algorithms_int_array";
+
+ /**
* List of supported integrity algorithms for IKE session. Possible values are
* {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE},
* {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96},
@@ -9156,9 +9202,13 @@
KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC});
defaults.putIntArray(
+ KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});
+ defaults.putIntArray(
KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY,
new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC});
defaults.putIntArray(
+ KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {});
+ defaults.putIntArray(
KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY,
new int[] {
SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96,
@@ -9207,6 +9257,10 @@
SaProposal.KEY_LEN_AES_192,
SaProposal.KEY_LEN_AES_256});
defaults.putIntArray(
+ KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});
+ defaults.putIntArray(
+ KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {});
+ defaults.putIntArray(
KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY,
new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC});
defaults.putIntArray(
diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java
index b00c573..71df1f2 100644
--- a/telephony/java/android/telephony/NumberVerificationCallback.java
+++ b/telephony/java/android/telephony/NumberVerificationCallback.java
@@ -20,6 +20,9 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A callback for number verification. After a request for number verification is received,
* the system will call {@link #onCallReceived(String)} if a phone call was received from a number
@@ -34,6 +37,7 @@
REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM,
REASON_IN_EMERGENCY_CALL},
prefix = {"REASON_"})
+ @Retention(RetentionPolicy.SOURCE)
@interface NumberVerificationFailureReason {}
/**
diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java
index b8c1ffe..14713c7 100644
--- a/telephony/java/android/telephony/PinResult.java
+++ b/telephony/java/android/telephony/PinResult.java
@@ -25,6 +25,8 @@
import com.android.internal.telephony.PhoneConstants;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -46,6 +48,7 @@
PIN_RESULT_TYPE_FAILURE,
PIN_RESULT_TYPE_ABORTED,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface PinResultType {}
/**
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 7f1c14b..b568f07 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -16,6 +16,8 @@
package android.telephony;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -37,8 +39,11 @@
import android.telephony.data.DataCallResponse;
import android.telephony.data.Qos;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -66,6 +71,53 @@
private final LinkProperties mLinkProperties;
private final ApnSetting mApnSetting;
private final Qos mDefaultQos;
+ private final @NetworkValidationStatus int mNetworkValidationStatus;
+
+ /** @hide */
+ @IntDef(prefix = "NETWORK_VALIDATION_", value = {
+ NETWORK_VALIDATION_UNSUPPORTED,
+ NETWORK_VALIDATION_NOT_REQUESTED,
+ NETWORK_VALIDATION_IN_PROGRESS,
+ NETWORK_VALIDATION_SUCCESS,
+ NETWORK_VALIDATION_FAILURE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NetworkValidationStatus {}
+
+ /**
+ * Unsupported. The unsupported state is used when the data network cannot support the network
+ * validation function for the current data connection state.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public static final int NETWORK_VALIDATION_UNSUPPORTED = 0;
+
+ /**
+ * Not Requested. The not requested status is used when the data network supports the network
+ * validation function, but no network validation is being performed yet.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1;
+
+ /**
+ * In progress. The in progress state is used when the network validation process for the data
+ * network is in progress. This state is followed by either success or failure.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public static final int NETWORK_VALIDATION_IN_PROGRESS = 2;
+
+ /**
+ * Success. The Success status is used when network validation has been completed for the data
+ * network and the result is successful.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public static final int NETWORK_VALIDATION_SUCCESS = 3;
+
+ /**
+ * Failure. The Failure status is used when network validation has been completed for the data
+ * network and the result is failure.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public static final int NETWORK_VALIDATION_FAILURE = 4;
/**
* Constructor
@@ -87,7 +139,7 @@
.setApnTypeBitmask(apnTypes)
.setApnName(apn)
.setEntryName(apn)
- .build(), null);
+ .build(), null, NETWORK_VALIDATION_UNSUPPORTED);
}
@@ -109,7 +161,8 @@
private PreciseDataConnectionState(@TransportType int transportType, int id,
@DataState int state, @NetworkType int networkType,
@Nullable LinkProperties linkProperties, @DataFailureCause int failCause,
- @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos) {
+ @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos,
+ @NetworkValidationStatus int networkValidationStatus) {
mTransportType = transportType;
mId = id;
mState = state;
@@ -118,6 +171,7 @@
mFailCause = failCause;
mApnSetting = apnSetting;
mDefaultQos = defaultQos;
+ mNetworkValidationStatus = networkValidationStatus;
}
/**
@@ -140,6 +194,7 @@
mDefaultQos = in.readParcelable(
Qos.class.getClassLoader(),
android.telephony.data.Qos.class);
+ mNetworkValidationStatus = in.readInt();
}
/**
@@ -289,6 +344,16 @@
return mDefaultQos;
}
+ /**
+ * Returns the network validation state.
+ *
+ * @return the network validation status of the data call
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public @NetworkValidationStatus int getNetworkValidationStatus() {
+ return mNetworkValidationStatus;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -304,6 +369,7 @@
out.writeInt(mFailCause);
out.writeParcelable(mApnSetting, flags);
out.writeParcelable(mDefaultQos, flags);
+ out.writeInt(mNetworkValidationStatus);
}
public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR
@@ -321,7 +387,7 @@
@Override
public int hashCode() {
return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause,
- mLinkProperties, mApnSetting, mDefaultQos);
+ mLinkProperties, mApnSetting, mDefaultQos, mNetworkValidationStatus);
}
@@ -337,7 +403,8 @@
&& mFailCause == that.mFailCause
&& Objects.equals(mLinkProperties, that.mLinkProperties)
&& Objects.equals(mApnSetting, that.mApnSetting)
- && Objects.equals(mDefaultQos, that.mDefaultQos);
+ && Objects.equals(mDefaultQos, that.mDefaultQos)
+ && mNetworkValidationStatus == that.mNetworkValidationStatus;
}
@NonNull
@@ -354,11 +421,34 @@
sb.append(", link properties: " + mLinkProperties);
sb.append(", default QoS: " + mDefaultQos);
sb.append(", fail cause: " + DataFailCause.toString(mFailCause));
+ sb.append(", network validation status: "
+ + networkValidationStatusToString(mNetworkValidationStatus));
return sb.toString();
}
/**
+ * Convert a network validation status to string.
+ *
+ * @param networkValidationStatus network validation status.
+ * @return string of validation status.
+ *
+ * @hide
+ */
+ @NonNull
+ public static String networkValidationStatusToString(
+ @NetworkValidationStatus int networkValidationStatus) {
+ switch (networkValidationStatus) {
+ case NETWORK_VALIDATION_UNSUPPORTED: return "unsupported";
+ case NETWORK_VALIDATION_NOT_REQUESTED: return "not requested";
+ case NETWORK_VALIDATION_IN_PROGRESS: return "in progress";
+ case NETWORK_VALIDATION_SUCCESS: return "success";
+ case NETWORK_VALIDATION_FAILURE: return "failure";
+ default: return Integer.toString(networkValidationStatus);
+ }
+ }
+
+ /**
* {@link PreciseDataConnectionState} builder
*
* @hide
@@ -394,6 +484,10 @@
/** The Default QoS for this EPS/5GS bearer or null otherwise */
private @Nullable Qos mDefaultQos;
+ /** The network validation status for the data connection. */
+ private @NetworkValidationStatus int mNetworkValidationStatus =
+ NETWORK_VALIDATION_UNSUPPORTED;
+
/**
* Set the transport type of the data connection.
*
@@ -486,13 +580,27 @@
}
/**
+ * Set the network validation state for the data connection.
+ *
+ * @param networkValidationStatus the network validation status of the data call
+ * @return The builder
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public @NonNull Builder setNetworkValidationStatus(
+ @NetworkValidationStatus int networkValidationStatus) {
+ mNetworkValidationStatus = networkValidationStatus;
+ return this;
+ }
+
+ /**
* Build the {@link PreciseDataConnectionState} instance.
*
* @return The {@link PreciseDataConnectionState} instance
*/
public PreciseDataConnectionState build() {
return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType,
- mLinkProperties, mFailCause, mApnSetting, mDefaultQos);
+ mLinkProperties, mFailCause, mApnSetting, mDefaultQos,
+ mNetworkValidationStatus);
}
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 1c5761d..b96914e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3400,6 +3400,7 @@
SIM_STATE_LOADED,
SIM_STATE_PRESENT,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface SimState {}
/**
@@ -10170,6 +10171,7 @@
CALL_COMPOSER_STATUS_ON,
CALL_COMPOSER_STATUS_OFF,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface CallComposerStatus {}
/**
@@ -13157,7 +13159,7 @@
CARRIER_RESTRICTION_STATUS_RESTRICTED,
CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER
})
-
+ @Retention(RetentionPolicy.SOURCE)
public @interface CarrierRestrictionStatus {
}
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index c7f0c5f..9dd83d1 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -17,6 +17,7 @@
package android.telephony.data;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -27,9 +28,11 @@
import android.os.Parcelable;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.DataFailCause;
+import android.telephony.PreciseDataConnectionState;
import android.telephony.data.ApnSetting.ProtocolType;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -123,7 +126,6 @@
* Indicates that the pdu session id is not set.
*/
public static final int PDU_SESSION_ID_NOT_SET = 0;
-
private final @DataFailureCause int mCause;
private final long mSuggestedRetryTime;
private final int mId;
@@ -143,6 +145,7 @@
private final List<QosBearerSession> mQosBearerSessions;
private final NetworkSliceInfo mSliceInfo;
private final List<TrafficDescriptor> mTrafficDescriptors;
+ private final @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus;
/**
* @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error.
@@ -185,7 +188,8 @@
HANDOVER_FAILURE_MODE_LEGACY, PDU_SESSION_ID_NOT_SET,
null /* defaultQos */, Collections.emptyList() /* qosBearerSessions */,
null /* sliceInfo */,
- Collections.emptyList() /* trafficDescriptors */);
+ Collections.emptyList(), /* trafficDescriptors */
+ PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
}
private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id,
@@ -196,7 +200,8 @@
@HandoverFailureMode int handoverFailureMode, int pduSessionId,
@Nullable Qos defaultQos, @NonNull List<QosBearerSession> qosBearerSessions,
@Nullable NetworkSliceInfo sliceInfo,
- @NonNull List<TrafficDescriptor> trafficDescriptors) {
+ @NonNull List<TrafficDescriptor> trafficDescriptors,
+ @PreciseDataConnectionState.NetworkValidationStatus int networkValidationStatus) {
mCause = cause;
mSuggestedRetryTime = suggestedRetryTime;
mId = id;
@@ -216,6 +221,7 @@
mQosBearerSessions = new ArrayList<>(qosBearerSessions);
mSliceInfo = sliceInfo;
mTrafficDescriptors = new ArrayList<>(trafficDescriptors);
+ mNetworkValidationStatus = networkValidationStatus;
if (mLinkStatus == LINK_STATUS_ACTIVE
|| mLinkStatus == LINK_STATUS_DORMANT) {
@@ -270,6 +276,7 @@
source.readList(mTrafficDescriptors,
TrafficDescriptor.class.getClassLoader(),
android.telephony.data.TrafficDescriptor.class);
+ mNetworkValidationStatus = source.readInt();
}
/**
@@ -442,6 +449,17 @@
return Collections.unmodifiableList(mTrafficDescriptors);
}
+ /**
+ * Return the network validation status that was initiated by {@link
+ * DataService.DataServiceProvider#requestValidation}
+ *
+ * @return The network validation status of data connection.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public @PreciseDataConnectionState.NetworkValidationStatus int getNetworkValidationStatus() {
+ return mNetworkValidationStatus;
+ }
+
@NonNull
@Override
public String toString() {
@@ -466,6 +484,8 @@
.append(" qosBearerSessions=").append(mQosBearerSessions)
.append(" sliceInfo=").append(mSliceInfo)
.append(" trafficDescriptors=").append(mTrafficDescriptors)
+ .append(" networkValidationStatus=").append(PreciseDataConnectionState
+ .networkValidationStatusToString(mNetworkValidationStatus))
.append("}");
return sb.toString();
}
@@ -504,7 +524,8 @@
&& mQosBearerSessions.containsAll(other.mQosBearerSessions) // non-null
&& Objects.equals(mSliceInfo, other.mSliceInfo)
&& mTrafficDescriptors.size() == other.mTrafficDescriptors.size() // non-null
- && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); // non-null
+ && mTrafficDescriptors.containsAll(other.mTrafficDescriptors) // non-null
+ && mNetworkValidationStatus == other.mNetworkValidationStatus;
}
@Override
@@ -513,7 +534,7 @@
mInterfaceName, Set.copyOf(mAddresses), Set.copyOf(mDnsAddresses),
Set.copyOf(mGatewayAddresses), Set.copyOf(mPcscfAddresses), mMtu, mMtuV4, mMtuV6,
mHandoverFailureMode, mPduSessionId, mDefaultQos, Set.copyOf(mQosBearerSessions),
- mSliceInfo, Set.copyOf(mTrafficDescriptors));
+ mSliceInfo, Set.copyOf(mTrafficDescriptors), mNetworkValidationStatus);
}
@Override
@@ -542,6 +563,7 @@
dest.writeList(mQosBearerSessions);
dest.writeParcelable(mSliceInfo, flags);
dest.writeList(mTrafficDescriptors);
+ dest.writeInt(mNetworkValidationStatus);
}
public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR =
@@ -629,6 +651,9 @@
private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
+ private @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus =
+ PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
+
/**
* Default constructor for Builder.
*/
@@ -905,6 +930,20 @@
}
/**
+ * Set the network validation status that corresponds to the state of the network validation
+ * request started by {@link DataService.DataServiceProvider#requestValidation}
+ *
+ * @param status The network validation status.
+ * @return The same instance of the builder.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public @NonNull Builder setNetworkValidationStatus(
+ @PreciseDataConnectionState.NetworkValidationStatus int status) {
+ mNetworkValidationStatus = status;
+ return this;
+ }
+
+ /**
* Build the DataCallResponse.
*
* @return the DataCallResponse object.
@@ -913,7 +952,8 @@
return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus,
mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses,
mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId,
- mDefaultQos, mQosBearerSessions, mSliceInfo, mTrafficDescriptors);
+ mDefaultQos, mQosBearerSessions, mSliceInfo, mTrafficDescriptors,
+ mNetworkValidationStatus);
}
}
}
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index d8b2cbe..80e91a3 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -16,6 +16,8 @@
package android.telephony.data;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -26,6 +28,7 @@
import android.content.Intent;
import android.net.LinkProperties;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
@@ -36,6 +39,9 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -44,6 +50,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Base class of data service. Services that extend DataService must register the service in
@@ -113,11 +121,14 @@
private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED = 14;
private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED = 15;
private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED = 16;
+ private static final int DATA_SERVICE_REQUEST_VALIDATION = 17;
private final HandlerThread mHandlerThread;
private final DataServiceHandler mHandler;
+ private final Executor mHandlerExecutor;
+
private final SparseArray<DataServiceProvider> mServiceMap = new SparseArray<>();
/** @hide */
@@ -379,6 +390,43 @@
}
}
+ /**
+ * Request validation check to see if the network is working properly for a given data call.
+ *
+ * <p>This request is completed immediately after submitting the request to the data service
+ * provider and receiving {@link DataServiceCallback.ResultCode}, and progress status or
+ * validation results are notified through {@link
+ * DataCallResponse#getNetworkValidationStatus}.
+ *
+ * <p> If the network validation request is submitted successfully, {@link
+ * DataServiceCallback#RESULT_SUCCESS} is passed to {@code resultCodeCallback}. If the
+ * network validation feature is not supported by the data service provider itself, {@link
+ * DataServiceCallback#RESULT_ERROR_UNSUPPORTED} is passed to {@code resultCodeCallback}.
+ * See {@link DataServiceCallback.ResultCode} for the type of response that indicates
+ * whether the request was successfully submitted or had an error.
+ *
+ * <p>In response to this network validation request, providers can validate the data call
+ * in their own way. For example, in IWLAN, the DPD (Dead Peer Detection) can be used as a
+ * tool to check whether a data call is alive.
+ *
+ * @param cid The identifier of the data call which is provided in {@link DataCallResponse}
+ * @param executor The callback executor for the response.
+ * @param resultCodeCallback Listener for the {@link DataServiceCallback.ResultCode} that
+ * request validation to the DataService and checks if the request has been submitted.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public void requestValidation(int cid,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
+
+ Log.d(TAG, "requestValidation: " + cid);
+
+ // The default implementation is to return unsupported.
+ executor.execute(() -> resultCodeCallback
+ .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED));
+ }
/**
* Notify the system that current data call list changed. Data service must invoke this
@@ -537,6 +585,17 @@
}
}
+ private static final class ValidationRequest {
+ public final int cid;
+ public final Executor executor;
+ public final IIntegerConsumer callback;
+ ValidationRequest(int cid, Executor executor, IIntegerConsumer callback) {
+ this.cid = cid;
+ this.executor = executor;
+ this.callback = callback;
+ }
+ }
+
private class DataServiceHandler extends Handler {
DataServiceHandler(Looper looper) {
@@ -679,6 +738,15 @@
loge("Failed to call onApnUnthrottled. " + e);
}
break;
+ case DATA_SERVICE_REQUEST_VALIDATION:
+ if (serviceProvider == null) break;
+ ValidationRequest validationRequest = (ValidationRequest) message.obj;
+ serviceProvider.requestValidation(
+ validationRequest.cid,
+ validationRequest.executor,
+ FunctionalUtils
+ .ignoreRemoteException(validationRequest.callback::accept));
+ break;
}
}
}
@@ -691,6 +759,7 @@
mHandlerThread.start();
mHandler = new DataServiceHandler(mHandlerThread.getLooper());
+ mHandlerExecutor = new HandlerExecutor(mHandler);
log("Data service created");
}
@@ -853,6 +922,18 @@
mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED,
slotIndex, 0, callback).sendToTarget();
}
+
+ @Override
+ public void requestValidation(int slotIndex, int cid, IIntegerConsumer resultCodeCallback) {
+ if (resultCodeCallback == null) {
+ loge("requestValidation: resultCodeCallback is null");
+ return;
+ }
+ ValidationRequest validationRequest =
+ new ValidationRequest(cid, mHandlerExecutor, resultCodeCallback);
+ mHandler.obtainMessage(DATA_SERVICE_REQUEST_VALIDATION,
+ slotIndex, 0, validationRequest).sendToTarget();
+ }
}
private void log(String s) {
diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl
index 1346946..15f8881 100644
--- a/telephony/java/android/telephony/data/IDataService.aidl
+++ b/telephony/java/android/telephony/data/IDataService.aidl
@@ -22,6 +22,8 @@
import android.telephony.data.NetworkSliceInfo;
import android.telephony.data.TrafficDescriptor;
+import com.android.internal.telephony.IIntegerConsumer;
+
/**
* {@hide}
*/
@@ -46,4 +48,5 @@
void cancelHandover(int slotId, int cid, IDataServiceCallback callback);
void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback);
+ void requestValidation(int slotId, int cid, IIntegerConsumer callback);
}
diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
index 32ffdbc..bdd212a 100644
--- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl
@@ -16,6 +16,8 @@
package android.telephony.data;
+import com.android.internal.telephony.IIntegerConsumer;
+
/**
* The qualified networks service call back interface
* @hide
@@ -23,4 +25,5 @@
oneway interface IQualifiedNetworksServiceCallback
{
void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes);
+ void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback);
}
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index 56f0f9f..c3ba092 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -16,6 +16,8 @@
package android.telephony.data;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.Service;
@@ -29,13 +31,23 @@
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.NetCapability;
+import android.telephony.PreciseDataConnectionState;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.util.FunctionalUtils;
import com.android.telephony.Rlog;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Base class of the qualified networks service, which is a vendor service providing up-to-date
@@ -69,6 +81,10 @@
private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4;
private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5;
private static final int QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED = 6;
+ private static final int QNS_REQUEST_NETWORK_VALIDATION = 7;
+
+ /** Feature flags */
+ private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl();
private final HandlerThread mHandlerThread;
@@ -208,6 +224,72 @@
}
/**
+ * Request network validation to the connected data network for given a network capability.
+ *
+ * <p>This network validation can only be performed when a data network is in connected
+ * state, and will not be triggered if the data network does not support network validation
+ * feature or network validation is not in connected state.
+ *
+ * <p>See {@link DataServiceCallback.ResultCode} for the type of response that indicates
+ * whether the request was successfully submitted or had an error.
+ *
+ * <p>If network validation is requested, monitor network validation status in {@link
+ * PreciseDataConnectionState#getNetworkValidationStatus()}.
+ *
+ * @param networkCapability A network capability. (Note that only APN-type capabilities are
+ * supported.
+ * @param executor executor The callback executor that responds whether the request has been
+ * successfully submitted or not.
+ * @param resultCodeCallback A callback to determine whether the request was successfully
+ * submitted or not.
+ */
+ @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION)
+ public void requestNetworkValidation(
+ @NetCapability int networkCapability,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null");
+
+ if (!sFeatureFlag.networkValidation()) {
+ loge("networkValidation feature is disabled");
+ executor.execute(
+ () ->
+ resultCodeCallback.accept(
+ DataServiceCallback.RESULT_ERROR_UNSUPPORTED));
+ return;
+ }
+
+ IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> resultCodeCallback.accept(result));
+ }
+ };
+
+ // Move to the internal handler and process it.
+ mHandler.obtainMessage(
+ QNS_REQUEST_NETWORK_VALIDATION,
+ mSlotIndex,
+ 0,
+ new NetworkValidationRequestData(networkCapability, callback))
+ .sendToTarget();
+ }
+
+ /** Process a network validation request on the internal handler. */
+ private void onRequestNetworkValidation(NetworkValidationRequestData data) {
+ try {
+ log("onRequestNetworkValidation");
+ // Callback to request a network validation.
+ mCallback.onNetworkValidationRequested(data.mNetworkCapability, data.mCallback);
+ } catch (RemoteException | NullPointerException e) {
+ loge("Failed to call onRequestNetworkValidation. " + e);
+ FunctionalUtils.ignoreRemoteException(data.mCallback::accept)
+ .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED);
+ }
+ }
+
+ /**
* Called when the qualified networks provider is removed. The extended class should
* implement this method to perform cleanup works.
*/
@@ -280,6 +362,10 @@
if (provider == null) break;
provider.onUpdateQualifiedNetworkTypes(message.arg2, (int[]) message.obj);
break;
+
+ case QNS_REQUEST_NETWORK_VALIDATION:
+ if (provider == null) break;
+ provider.onRequestNetworkValidation((NetworkValidationRequestData) message.obj);
}
}
}
@@ -364,6 +450,17 @@
}
}
+ private static final class NetworkValidationRequestData {
+ final @NetCapability int mNetworkCapability;
+ final IIntegerConsumer mCallback;
+
+ private NetworkValidationRequestData(@NetCapability int networkCapability,
+ @NonNull IIntegerConsumer callback) {
+ mNetworkCapability = networkCapability;
+ mCallback = callback;
+ }
+ }
+
private void log(String s) {
Rlog.d(TAG, s);
}
diff --git a/telephony/java/android/telephony/data/ThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java
index 0335c68..0dff6ff 100644
--- a/telephony/java/android/telephony/data/ThrottleStatus.java
+++ b/telephony/java/android/telephony/data/ThrottleStatus.java
@@ -27,6 +27,8 @@
import android.telephony.AccessNetworkConstants;
import android.telephony.Annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -52,6 +54,7 @@
ThrottleStatus.THROTTLE_TYPE_NONE,
ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface ThrottleType {
}
@@ -76,6 +79,7 @@
ThrottleStatus.RETRY_TYPE_NEW_CONNECTION,
ThrottleStatus.RETRY_TYPE_HANDOVER,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface RetryType {
}
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java
index 76394fe..e2df0d4 100644
--- a/telephony/java/android/telephony/ims/MediaQualityStatus.java
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.telephony.AccessNetworkConstants.TransportType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -49,6 +51,7 @@
MEDIA_SESSION_TYPE_AUDIO,
MEDIA_SESSION_TYPE_VIDEO,
})
+ @Retention(RetentionPolicy.SOURCE)
public @interface MediaSessionType {}
/**
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index f367e40..39c9d8b 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -22,6 +22,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -35,6 +37,7 @@
/**@hide*/
@StringDef(prefix = "RCS_PROFILE_",
value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4})
+ @Retention(RetentionPolicy.SOURCE)
public @interface StringRcsProfile {}
/**
diff --git a/tests/CtsSurfaceControlTestsStaging/OWNERS b/tests/CtsSurfaceControlTestsStaging/OWNERS
index 438b7f2..8ad3446 100644
--- a/tests/CtsSurfaceControlTestsStaging/OWNERS
+++ b/tests/CtsSurfaceControlTestsStaging/OWNERS
@@ -1 +1 @@
-include platform/cts:/tests/tests/graphics/OWNERS
\ No newline at end of file
+include platform/cts:/tests/tests/graphics/src/android/graphics/OWNERS
\ No newline at end of file
diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp
index 6cda8f0..b4a4359 100644
--- a/tests/MotionPrediction/Android.bp
+++ b/tests/MotionPrediction/Android.bp
@@ -26,5 +26,8 @@
android_app {
name: "MotionPrediction",
srcs: ["**/*.kt"],
+ kotlincflags: [
+ "-Werror",
+ ],
sdk_version: "current",
}
diff --git a/tests/MultiDeviceInput/Android.bp b/tests/MultiDeviceInput/Android.bp
new file mode 100644
index 0000000..3c80873
--- /dev/null
+++ b/tests/MultiDeviceInput/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "MultiDeviceInput",
+ srcs: ["**/*.kt"],
+ kotlincflags: [
+ "-Werror",
+ ],
+ sdk_version: "current",
+}
diff --git a/tests/MultiDeviceInput/AndroidManifest.xml b/tests/MultiDeviceInput/AndroidManifest.xml
new file mode 100644
index 0000000..ed8cadb
--- /dev/null
+++ b/tests/MultiDeviceInput/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.multideviceinput">
+
+ <application android:allowBackup="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:supportsRtl="true"
+ android:theme="@style/AppTheme">
+ <activity android:name=".MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/tests/MultiDeviceInput/OWNERS b/tests/MultiDeviceInput/OWNERS
new file mode 100644
index 0000000..c88bfe9
--- /dev/null
+++ b/tests/MultiDeviceInput/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/MultiDeviceInput/README.md b/tests/MultiDeviceInput/README.md
new file mode 100644
index 0000000..5fcdeda
--- /dev/null
+++ b/tests/MultiDeviceInput/README.md
@@ -0,0 +1,19 @@
+# MultiDeviceInput test app #
+
+This demo app is for manual testing of the multi-device input feature.
+It creates two windows - one on the left and one on the right. You can use different input devices
+in these windows.
+
+## Installation ##
+Install this using:
+```
+APP=MultiDeviceInput; m $APP && adb install $ANDROID_PRODUCT_OUT/system/app/$APP/$APP.apk
+```
+
+## Features ##
+
+* Touch in one window, use stylus in another window, at the same time
+* Visualize hovering stylus
+* Pinch zoom in one window to affect the line thickness in another window
+* Check whether stylus rejects touch in the same window
+* (in the future) Check stylus and touch operation in the same window
diff --git a/tests/MultiDeviceInput/res/layout/activity_main.xml b/tests/MultiDeviceInput/res/layout/activity_main.xml
new file mode 100644
index 0000000..a6a6f891
--- /dev/null
+++ b/tests/MultiDeviceInput/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ tools:context="test.multideviceinput.MainActivity">
+
+</LinearLayout>
diff --git a/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MultiDeviceInput/res/values-w820dp/dimens.xml b/tests/MultiDeviceInput/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..b14a560
--- /dev/null
+++ b/tests/MultiDeviceInput/res/values-w820dp/dimens.xml
@@ -0,0 +1,20 @@
+<!-- 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.
+-->
+<resources>
+ <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+ (such as screen margins) for screens with more than 820dp of available width. This
+ would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+ <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/MultiDeviceInput/res/values/colors.xml b/tests/MultiDeviceInput/res/values/colors.xml
new file mode 100644
index 0000000..c37df9f
--- /dev/null
+++ b/tests/MultiDeviceInput/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <color name="colorPrimary">#3F51B5</color>
+ <color name="colorPrimaryDark">#303F9F</color>
+ <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/MultiDeviceInput/res/values/dimens.xml b/tests/MultiDeviceInput/res/values/dimens.xml
new file mode 100644
index 0000000..bdb8ede
--- /dev/null
+++ b/tests/MultiDeviceInput/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<!-- 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.
+-->
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/tests/MultiDeviceInput/res/values/strings.xml b/tests/MultiDeviceInput/res/values/strings.xml
new file mode 100644
index 0000000..3827c34
--- /dev/null
+++ b/tests/MultiDeviceInput/res/values/strings.xml
@@ -0,0 +1,17 @@
+<!-- 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.
+-->
+<resources>
+ <string name="app_name">Simultaneous touch and stylus</string>
+</resources>
diff --git a/tests/MultiDeviceInput/res/values/styles.xml b/tests/MultiDeviceInput/res/values/styles.xml
new file mode 100644
index 0000000..a563e7e
--- /dev/null
+++ b/tests/MultiDeviceInput/res/values/styles.xml
@@ -0,0 +1,23 @@
+<!-- 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.
+-->
+<resources>
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+ <!-- Customize your theme here. -->
+ <item name="android:colorPrimary">@color/colorPrimary</item>
+ <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="android:colorAccent">@color/colorAccent</item>
+ </style>
+</resources>
diff --git a/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt b/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt
new file mode 100644
index 0000000..b5bd9ca
--- /dev/null
+++ b/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt
@@ -0,0 +1,183 @@
+/*
+ * 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 test.multideviceinput
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.InputDevice.SOURCE_STYLUS
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_HOVER_EXIT
+import android.view.MotionEvent.ACTION_UP
+import android.view.ScaleGestureDetector
+import android.view.View
+
+import java.util.Vector
+
+private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) {
+ // Correct implementation here would require us to build a set of pointers and then iterate
+ // through them. Instead, we are taking a few shortcuts and ignore some of the events, which
+ // causes occasional gaps in the drawings.
+ if (from.pointerCount != to.pointerCount) {
+ return
+ }
+ // Now, 'from' is guaranteed to have as many pointers as the 'to' event. It doesn't
+ // necessarily mean they are the same pointers, though.
+ for (p in 0..<from.pointerCount) {
+ val x0 = from.getX(p)
+ val y0 = from.getY(p)
+ if (to.getPointerId(p) == from.getPointerId(p)) {
+ // This only works when the i-th pointer in "to" is the same pointer
+ // as the i-th pointer in "from"`. It's not guaranteed by the input APIs,
+ // but it works in practice.
+ val x1 = to.getX(p)
+ val y1 = to.getY(p)
+ // Ignoring historical data here for simplicity
+ canvas.drawLine(x0, y0, x1, y1, paint)
+ }
+ }
+}
+
+private fun drawCircle(canvas: Canvas, event: MotionEvent, paint: Paint, radius: Float) {
+ val x = event.getX()
+ val y = event.getY()
+ canvas.drawCircle(x, y, radius, paint)
+}
+
+/**
+ * Draw the current stroke
+ */
+class DrawingView : View {
+ private val TAG = "DrawingView"
+
+ private var myState: SharedScaledPointerSize? = null
+ private var otherState: SharedScaledPointerSize? = null
+
+ constructor(
+ context: Context,
+ myState: SharedScaledPointerSize,
+ otherState: SharedScaledPointerSize
+ ) : super(context) {
+ this.myState = myState
+ this.otherState = otherState
+ init()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ init()
+ }
+
+ val touchEvents = mutableMapOf<Int, Vector<Pair<MotionEvent, Paint>>>()
+ val hoverEvents = mutableMapOf<Int, MotionEvent>()
+
+ val scaleGestureListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+
+ override fun onScaleBegin(scaleGestureDetector: ScaleGestureDetector): Boolean {
+ return true
+ }
+
+ override fun onScale(scaleGestureDetector: ScaleGestureDetector): Boolean {
+ val scaleFactor = scaleGestureDetector.scaleFactor
+ when (otherState?.state) {
+ PointerState.DOWN -> {
+ otherState?.lineSize = (otherState?.lineSize ?: 5f) * scaleFactor
+ }
+ PointerState.HOVER -> {
+ otherState?.circleSize = (otherState?.circleSize ?: 20f) * scaleFactor
+ }
+ else -> {}
+ }
+ return true
+ }
+ }
+ private val scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener, null)
+
+ private var touchPaint = Paint()
+ private var stylusPaint = Paint()
+
+ private fun init() {
+ touchPaint.color = Color.RED
+ touchPaint.setStrokeWidth(5f)
+ stylusPaint.color = Color.YELLOW
+ stylusPaint.setStrokeWidth(5f)
+
+ setOnHoverListener { _, event -> processHoverEvent(event); true }
+ }
+
+ private fun processTouchEvent(event: MotionEvent) {
+ scaleGestureDetector.onTouchEvent(event)
+ if (event.actionMasked == ACTION_DOWN) {
+ touchEvents.remove(event.deviceId)
+ myState?.state = PointerState.DOWN
+ } else if (event.actionMasked == ACTION_UP) {
+ myState?.state = PointerState.NONE
+ }
+ var vec = touchEvents.getOrPut(event.deviceId) { Vector<Pair<MotionEvent, Paint>>() }
+
+ val paint = if (event.isFromSource(SOURCE_STYLUS)) {
+ val size = myState?.lineSize ?: 5f
+ stylusPaint.setStrokeWidth(size)
+ Paint(stylusPaint)
+ } else {
+ val size = myState?.lineSize ?: 5f
+ touchPaint.setStrokeWidth(size)
+ Paint(touchPaint)
+ }
+ vec.add(Pair(MotionEvent.obtain(event), paint))
+ invalidate()
+ }
+
+ private fun processHoverEvent(event: MotionEvent) {
+ hoverEvents.remove(event.deviceId)
+ if (event.getActionMasked() != ACTION_HOVER_EXIT) {
+ hoverEvents.put(event.deviceId, MotionEvent.obtain(event))
+ myState?.state = PointerState.HOVER
+ } else {
+ myState?.state = PointerState.NONE
+ }
+ invalidate()
+ }
+
+ public override fun onTouchEvent(event: MotionEvent): Boolean {
+ processTouchEvent(event)
+ return true
+ }
+
+ public override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ // Draw touch and stylus MotionEvents
+ for ((_, vec) in touchEvents ) {
+ for (i in 1 until vec.size) {
+ drawLine(canvas, vec[i - 1].first, vec[i].first, vec[i].second)
+ }
+ }
+ // Draw hovers
+ for ((_, event) in hoverEvents ) {
+ if (event.isFromSource(SOURCE_STYLUS)) {
+ val size = myState?.circleSize ?: 20f
+ drawCircle(canvas, event, stylusPaint, size)
+ } else {
+ val size = myState?.circleSize ?: 20f
+ drawCircle(canvas, event, touchPaint, size)
+ }
+ }
+ }
+}
diff --git a/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt b/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt
new file mode 100644
index 0000000..9112085
--- /dev/null
+++ b/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt
@@ -0,0 +1,83 @@
+/*
+ * 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 test.multideviceinput
+
+import android.app.Activity
+import android.graphics.Color
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets.Type
+import android.view.WindowManager
+
+
+enum class PointerState {
+ DOWN, // One or more pointer(s) down, lines are being drawn
+ HOVER, // Pointer is hovering
+ NONE, // Nothing is touching or hovering
+}
+
+data class SharedScaledPointerSize(
+ var lineSize: Float,
+ var circleSize: Float,
+ var state: PointerState
+)
+
+class MainActivity : Activity() {
+ val TAG = "MultiDeviceInput"
+ private val leftState = SharedScaledPointerSize(5f, 20f, PointerState.NONE)
+ private val rightState = SharedScaledPointerSize(5f, 20f, PointerState.NONE)
+ private lateinit var left: View
+ private lateinit var right: View
+
+ override fun onResume() {
+ super.onResume()
+
+ val wm = getSystemService(WindowManager::class.java)
+ val wmlp = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION)
+ wmlp.flags = (wmlp.flags or
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+
+ val windowMetrics = windowManager.currentWindowMetrics
+ val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(Type.systemBars())
+ val width = windowMetrics.bounds.width() - insets.left - insets.right
+ val height = windowMetrics.bounds.height() - insets.top - insets.bottom
+
+ wmlp.width = width * 24 / 50
+ wmlp.height = height * 35 / 50
+
+ val vglp = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+
+ wmlp.setTitle("Left -- " + getPackageName())
+ wmlp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+ left = DrawingView(this, leftState, rightState)
+ left.setBackgroundColor(Color.LTGRAY)
+ left.setLayoutParams(vglp)
+ wm.addView(left, wmlp)
+
+ wmlp.setTitle("Right -- " + getPackageName())
+ wmlp.gravity = Gravity.CENTER_VERTICAL or Gravity.END
+ right = DrawingView(this, rightState, leftState)
+ right.setBackgroundColor(Color.LTGRAY)
+ right.setLayoutParams(vglp)
+ wm.addView(right, wmlp)
+ }
+}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index fff8f78..412aa9b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -120,6 +120,7 @@
"io/Util.cpp",
"io/ZipArchive.cpp",
"link/AutoVersioner.cpp",
+ "link/FeatureFlagsFilter.cpp",
"link/ManifestFixer.cpp",
"link/NoDefaultResourceRemover.cpp",
"link/PrivateAttributeMover.cpp",
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
new file mode 100644
index 0000000..fdf3f74
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#include "link/FeatureFlagsFilter.h"
+
+#include <string_view>
+
+#include "androidfw/IDiagnostics.h"
+#include "androidfw/Source.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
+
+using ::aapt::xml::Element;
+using ::aapt::xml::Node;
+using ::aapt::xml::NodeCast;
+
+namespace aapt {
+
+class FlagsVisitor : public xml::Visitor {
+ public:
+ explicit FlagsVisitor(android::IDiagnostics* diagnostics,
+ const FeatureFlagValues& feature_flag_values,
+ const FeatureFlagsFilterOptions& options)
+ : diagnostics_(diagnostics), feature_flag_values_(feature_flag_values), options_(options) {
+ }
+
+ void Visit(xml::Element* node) override {
+ std::erase_if(node->children,
+ [this](std::unique_ptr<xml::Node>& node) { return ShouldRemove(node); });
+ VisitChildren(node);
+ }
+
+ bool HasError() const {
+ return has_error_;
+ }
+
+ private:
+ bool ShouldRemove(std::unique_ptr<xml::Node>& node) {
+ if (const auto* el = NodeCast<Element>(node.get())) {
+ auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag");
+ if (attr == nullptr) {
+ return false;
+ }
+
+ bool negated = false;
+ std::string_view flag_name = util::TrimWhitespace(attr->value);
+ if (flag_name.starts_with('!')) {
+ negated = true;
+ flag_name = flag_name.substr(1);
+ }
+
+ if (auto it = feature_flag_values_.find(std::string(flag_name));
+ it != feature_flag_values_.end()) {
+ if (it->second.has_value()) {
+ if (options_.remove_disabled_elements) {
+ // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
+ return *it->second == negated;
+ }
+ } else if (options_.flags_must_have_value) {
+ diagnostics_->Error(android::DiagMessage(node->line_number)
+ << "attribute 'android:featureFlag' has flag '" << flag_name
+ << "' without a true/false value from --feature_flags parameter");
+ has_error_ = true;
+ return false;
+ }
+ } else if (options_.fail_on_unrecognized_flags) {
+ diagnostics_->Error(android::DiagMessage(node->line_number)
+ << "attribute 'android:featureFlag' has flag '" << flag_name
+ << "' not found in flags from --feature_flags parameter");
+ has_error_ = true;
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ android::IDiagnostics* diagnostics_;
+ const FeatureFlagValues& feature_flag_values_;
+ const FeatureFlagsFilterOptions& options_;
+ bool has_error_ = false;
+};
+
+bool FeatureFlagsFilter::Consume(IAaptContext* context, xml::XmlResource* doc) {
+ FlagsVisitor visitor(context->GetDiagnostics(), feature_flag_values_, options_);
+ doc->root->Accept(&visitor);
+ return !visitor.HasError();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/FeatureFlagsFilter.h b/tools/aapt2/link/FeatureFlagsFilter.h
new file mode 100644
index 0000000..1d342a7
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "android-base/macros.h"
+#include "cmd/Util.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+struct FeatureFlagsFilterOptions {
+ // If true, elements whose featureFlag values are false (i.e., disabled feature) will be removed.
+ bool remove_disabled_elements = true;
+
+ // If true, `Consume()` will return false (error) if a flag was found that is not in
+ // `feature_flag_values`.
+ bool fail_on_unrecognized_flags = true;
+
+ // If true, `Consume()` will return false (error) if a flag was found whose value in
+ // `feature_flag_values` is not defined (std::nullopt).
+ bool flags_must_have_value = true;
+};
+
+// Looks for the `android:featureFlag` attribute in each XML element, validates the flag names and
+// values, and removes elements according to the values in `feature_flag_values`. An element will be
+// removed if the flag's given value is FALSE. A "!" before the flag name in the attribute indicates
+// a boolean NOT operation, i.e., an element will be removed if the flag's given value is TRUE. For
+// example, if the XML is the following:
+//
+// <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+// <permission android:name="FOO" android:featureFlag="!flag"
+// android:protectionLevel="normal" />
+// <permission android:name="FOO" android:featureFlag="flag"
+// android:protectionLevel="dangerous" />
+// </manifest>
+//
+// If `feature_flag_values` contains {"flag", true}, then the <permission> element with
+// protectionLevel="normal" will be removed, and the <permission> element with
+// protectionLevel="normal" will be kept.
+//
+// The `Consume()` function will return false if there is an invalid flag found (see
+// FeatureFlagsFilterOptions for customizing the filter's validation behavior). Do not use the XML
+// further if there are errors as there may be elements removed already.
+class FeatureFlagsFilter : public IXmlResourceConsumer {
+ public:
+ explicit FeatureFlagsFilter(FeatureFlagValues feature_flag_values,
+ FeatureFlagsFilterOptions options)
+ : feature_flag_values_(std::move(feature_flag_values)), options_(options) {
+ }
+
+ bool Consume(IAaptContext* context, xml::XmlResource* doc) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FeatureFlagsFilter);
+
+ const FeatureFlagValues feature_flag_values_;
+ const FeatureFlagsFilterOptions options_;
+};
+
+} // namespace aapt
diff --git a/tools/aapt2/link/FeatureFlagsFilter_test.cpp b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
new file mode 100644
index 0000000..53086cc
--- /dev/null
+++ b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+#include "link/FeatureFlagsFilter.h"
+
+#include <string_view>
+
+#include "test/Test.h"
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace aapt {
+
+// Returns null if there was an error from FeatureFlagsFilter.
+std::unique_ptr<xml::XmlResource> VerifyWithOptions(std::string_view str,
+ const FeatureFlagValues& feature_flag_values,
+ const FeatureFlagsFilterOptions& options) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
+ FeatureFlagsFilter filter(feature_flag_values, options);
+ if (filter.Consume(test::ContextBuilder().Build().get(), doc.get())) {
+ return doc;
+ }
+ return {};
+}
+
+// Returns null if there was an error from FeatureFlagsFilter.
+std::unique_ptr<xml::XmlResource> Verify(std::string_view str,
+ const FeatureFlagValues& feature_flag_values) {
+ return VerifyWithOptions(str, feature_flag_values, {});
+}
+
+TEST(FeatureFlagsFilterTest, NoFeatureFlagAttributes) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" />
+ </manifest>)EOF",
+ {{"flag", false}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+TEST(FeatureFlagsFilterTest, RemoveElementWithDisabledFlag) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="flag" />
+ </manifest>)EOF",
+ {{"flag", false}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, RemoveElementWithNegatedEnabledFlag) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="!flag" />
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, KeepElementWithEnabledFlag) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="flag" />
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, SideBySideEnabledAndDisabled) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="!flag"
+ android:protectionLevel="normal" />
+ <permission android:name="FOO" android:featureFlag="flag"
+ android:protectionLevel="dangerous" />
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto children = root->GetChildElements();
+ ASSERT_EQ(children.size(), 1);
+ auto attr = children[0]->FindAttribute(xml::kSchemaAndroid, "protectionLevel");
+ ASSERT_THAT(attr, NotNull());
+ ASSERT_EQ(attr->value, "dangerous");
+}
+
+TEST(FeatureFlagsFilterTest, RemoveDeeplyNestedElement) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <application>
+ <provider />
+ <activity>
+ <layout android:featureFlag="!flag" />
+ </activity>
+ </application>
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto application = root->FindChild({}, "application");
+ ASSERT_THAT(application, NotNull());
+ auto activity = application->FindChild({}, "activity");
+ ASSERT_THAT(activity, NotNull());
+ auto maybe_removed = activity->FindChild({}, "layout");
+ ASSERT_THAT(maybe_removed, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, KeepDeeplyNestedElement) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <application>
+ <provider />
+ <activity>
+ <layout android:featureFlag="flag" />
+ </activity>
+ </application>
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto application = root->FindChild({}, "application");
+ ASSERT_THAT(application, NotNull());
+ auto activity = application->FindChild({}, "activity");
+ ASSERT_THAT(activity, NotNull());
+ auto maybe_removed = activity->FindChild({}, "layout");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnEmptyFeatureFlagAttribute) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag=" " />
+ </manifest>)EOF",
+ {{"flag", false}});
+ ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnFlagWithNoGivenValue) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="flag" />
+ </manifest>)EOF",
+ {{"flag", std::nullopt}});
+ ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnUnrecognizedFlag) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="unrecognized" />
+ </manifest>)EOF",
+ {{"flag", true}});
+ ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, FailOnMultipleValidationErrors) {
+ auto doc = Verify(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="bar" />
+ <permission android:name="FOO" android:featureFlag="unrecognized" />
+ </manifest>)EOF",
+ {{"flag", std::nullopt}});
+ ASSERT_THAT(doc, IsNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionRemoveDisabledElementsIsFalse) {
+ auto doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="flag" />
+ </manifest>)EOF",
+ {{"flag", false}}, {.remove_disabled_elements = false});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionFlagsMustHaveValueIsFalse) {
+ auto doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="flag" />
+ </manifest>)EOF",
+ {{"flag", std::nullopt}}, {.flags_must_have_value = false});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+TEST(FeatureFlagsFilterTest, OptionFailOnUnrecognizedFlagsIsFalse) {
+ auto doc = VerifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
+ <permission android:name="FOO" android:featureFlag="unrecognized" />
+ </manifest>)EOF",
+ {{"flag", true}}, {.fail_on_unrecognized_flags = false});
+ ASSERT_THAT(doc, NotNull());
+ auto root = doc->root.get();
+ ASSERT_THAT(root, NotNull());
+ auto maybe_removed = root->FindChild({}, "permission");
+ ASSERT_THAT(maybe_removed, NotNull());
+}
+
+} // namespace aapt