Merge "Require permission to unlock keyguard" into main
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 1ce8f9f..bdec8b9 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -59,22 +59,25 @@
and `"bluetooth"`.
Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*`
-control code, and data is a vector of control values to be sent to the uinput device, which depends
-on the control code.
+control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of
+control values to be sent to the uinput device, which depends on the control code.
-| Field | Type | Description |
-|:-------------:|:-------------:|:-------------------------- |
-| `type` | integer | `UI_SET_` control type |
-| `data` | integer array | control values |
+| Field | Type | Description |
+|:-------------:|:---------------------:|:-----------------------|
+| `type` | integer\|string | `UI_SET_` control type |
+| `data` | integer\|string array | control values |
+
+Due to the sequential nature in which this is parsed, the `type` field must be specified before
+the `data` field in this JSON Object.
`ff_effects_max` must be provided if `UI_SET_FFBIT` is used in `configuration`.
`abs_info` fields are provided to set the device axes information. It is an array of below objects:
-| Field | Type | Description |
-|:-------------:|:-------------:|:-------------------------- |
-| `code` | integer | Axis code |
-| `info` | object | Axis information object |
+| Field | Type | Description |
+|:-------------:|:---------------:|:------------------------|
+| `code` | integer\|string | Axis code or label |
+| `info` | object | Axis information object |
The axis information object is defined as below, with the fields having the same meaning as those
Linux's [`struct input_absinfo`][struct input_absinfo]:
@@ -99,16 +102,17 @@
"pid": 0x2c42,
"bus": "usb",
"configuration":[
- {"type":100, "data":[1, 21]}, // UI_SET_EVBIT : EV_KEY and EV_FF
- {"type":101, "data":[11, 2, 3, 4]}, // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
- {"type":107, "data":[80]} // UI_SET_FFBIT : FF_RUMBLE
+ {"type":"UI_SET_EVBIT", "data":["EV_KEY", "EV_FF"]},
+ {"type":"UI_SET_KEYBIT", "data":["KEY_0", "KEY_1", "KEY_2", "KEY_3"]},
+ {"type":"UI_SET_ABSBIT", "data":["ABS_Y", "ABS_WHEEL"]},
+ {"type":"UI_SET_FFBIT", "data":["FF_RUMBLE"]}
],
"ff_effects_max" : 1,
"abs_info": [
- {"code":1, "info": {"value":20, "minimum":-255,
+ {"code":"ABS_Y", "info": {"value":20, "minimum":-255,
"maximum":255, "fuzz":0, "flat":0, "resolution":1}
},
- {"code":8, "info": {"value":-50, "minimum":-255,
+ {"code":"ABS_WHEEL", "info": {"value":-50, "minimum":-255,
"maximum":255, "fuzz":0, "flat":0, "resolution":1}
}
]
@@ -157,11 +161,11 @@
Send an array of uinput event packets to the uinput device
-| Field | Type | Description |
-|:-------------:|:-------------:|:-------------------------- |
-| `id` | integer | Device ID |
-| `command` | string | Must be set to "inject" |
-| `events` | integer array | events to inject |
+| Field | Type | Description |
+|:-------------:|:---------------------:|:-------------------------- |
+| `id` | integer | Device ID |
+| `command` | string | Must be set to "inject" |
+| `events` | integer\|string array | events to inject |
The `events` parameter is an array of integers in sets of three: a type, an axis code, and an axis
value, like you'd find in Linux's `struct input_event`. For example, sending presses of the 0 and 1
@@ -171,14 +175,14 @@
{
"id": 1,
"command": "inject",
- "events": [0x01, 0xb, 0x1, // EV_KEY, KEY_0, DOWN
- 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0
- 0x01, 0x0b, 0x00, // EV_KEY, KEY_0, UP
- 0x00, 0x00, 0x00, // EV_SYN, SYN_REPORT, 0
- 0x01, 0x2, 0x1, // EV_KEY, KEY_1, DOWN
- 0x00, 0x00, 0x01, // EV_SYN, SYN_REPORT, 0
- 0x01, 0x02, 0x00, // EV_KEY, KEY_1, UP
- 0x00, 0x00, 0x01 // EV_SYN, SYN_REPORT, 0
+ "events": ["EV_KEY", "KEY_0", 1,
+ "EV_SYN", "SYN_REPORT", 0,
+ "EV_KEY", "KEY_0", 0,
+ "EV_SYN", "SYN_REPORT", 0,
+ "EV_KEY", "KEY_1", 1,
+ "EV_SYN", "SYN_REPORT", 0,
+ "EV_KEY", "KEY_1", 0,
+ "EV_SYN", "SYN_REPORT", 0
]
}
```
diff --git a/cmds/uinput/jni/Android.bp b/cmds/uinput/jni/Android.bp
index c56adc3..558bcc5 100644
--- a/cmds/uinput/jni/Android.bp
+++ b/cmds/uinput/jni/Android.bp
@@ -21,6 +21,7 @@
"libbase",
"libbinder",
"liblog",
+ "libinput",
"libnativehelper",
],
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index 3f4163d..7659054 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -16,12 +16,24 @@
#define LOG_TAG "UinputCommandDevice"
-#include <linux/uinput.h>
+#include "com_android_commands_uinput_Device.h"
+#include <android-base/stringprintf.h>
+#include <android/looper.h>
+#include <android_os_Parcel.h>
#include <fcntl.h>
+#include <input/InputEventLabels.h>
#include <inttypes.h>
+#include <jni.h>
+#include <linux/uinput.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
#include <time.h>
#include <unistd.h>
+
#include <algorithm>
#include <array>
#include <cstdio>
@@ -30,19 +42,6 @@
#include <memory>
#include <vector>
-#include <android/looper.h>
-#include <android_os_Parcel.h>
-#include <jni.h>
-#include <log/log.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#include <android-base/stringprintf.h>
-
-#include "com_android_commands_uinput_Device.h"
-
namespace android {
namespace uinput {
@@ -307,6 +306,21 @@
::ioctl(static_cast<int>(handle), UI_ABS_SETUP, &absSetup);
}
+static jint getEvdevEventTypeByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) {
+ ScopedUtfChars label(env, rawLabel);
+ return InputEventLookup::getLinuxEvdevEventTypeByLabel(label.c_str()).value_or(-1);
+}
+
+static jint getEvdevEventCodeByLabel(JNIEnv* env, jclass /* clazz */, jint type, jstring rawLabel) {
+ ScopedUtfChars label(env, rawLabel);
+ return InputEventLookup::getLinuxEvdevEventCodeByLabel(type, label.c_str()).value_or(-1);
+}
+
+static jint getEvdevInputPropByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) {
+ ScopedUtfChars label(env, rawLabel);
+ return InputEventLookup::getLinuxEvdevInputPropByLabel(label.c_str()).value_or(-1);
+}
+
static JNINativeMethod sMethods[] = {
{"nativeOpenUinputDevice",
"(Ljava/lang/String;IIIIILjava/lang/String;"
@@ -316,6 +330,12 @@
{"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
{"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
{"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
+ {"nativeGetEvdevEventTypeByLabel", "(Ljava/lang/String;)I",
+ reinterpret_cast<void*>(getEvdevEventTypeByLabel)},
+ {"nativeGetEvdevEventCodeByLabel", "(ILjava/lang/String;)I",
+ reinterpret_cast<void*>(getEvdevEventCodeByLabel)},
+ {"nativeGetEvdevInputPropByLabel", "(Ljava/lang/String;)I",
+ reinterpret_cast<void*>(getEvdevInputPropByLabel)},
};
int register_com_android_commands_uinput_Device(JNIEnv* env) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 732b33d..6458eef 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -66,6 +66,9 @@
private static native void nativeInjectEvent(long ptr, int type, int code, int value);
private static native void nativeConfigure(int handle, int code, int[] configs);
private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
+ private static native int nativeGetEvdevEventTypeByLabel(String label);
+ private static native int nativeGetEvdevEventCodeByLabel(int type, String label);
+ private static native int nativeGetEvdevInputPropByLabel(String label);
public Device(int id, String name, int vid, int pid, int bus,
SparseArray<int[]> configuration, int ffEffectsMax,
@@ -234,4 +237,32 @@
msg.sendToTarget();
}
}
+
+ static int getEvdevEventTypeByLabel(String label) {
+ final var type = nativeGetEvdevEventTypeByLabel(label);
+ if (type < 0) {
+ throw new IllegalArgumentException(
+ "Failed to get evdev event type from label: " + label);
+ }
+ return type;
+ }
+
+ static int getEvdevEventCodeByLabel(int type, String label) {
+ final var code = nativeGetEvdevEventCodeByLabel(type, label);
+ if (code < 0) {
+ throw new IllegalArgumentException(
+ "Failed to get evdev event code for type " + type + " from label: " + label);
+ }
+ return code;
+
+ }
+
+ static int getEvdevInputPropByLabel(String label) {
+ final var prop = nativeGetEvdevInputPropByLabel(label);
+ if (prop < 0) {
+ throw new IllegalArgumentException(
+ "Failed to get evdev input prop from label: " + label);
+ }
+ return prop;
+ }
}
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 4b090f5..cddb407 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -25,6 +25,9 @@
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;
@@ -39,7 +42,35 @@
public static final String COMMAND_REGISTER = "register";
public static final String COMMAND_DELAY = "delay";
public static final String COMMAND_INJECT = "inject";
- private static final int ABS_CNT = 64;
+ 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;
+
+ 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);
+
+ final String mName;
+ final int mValue;
+
+ UinputControlCode(String name, int value) {
+ this.mName = name;
+ this.mValue = value;
+ }
+ }
// These constants come from "include/uapi/linux/input.h" in the kernel
enum Bus {
@@ -257,8 +288,8 @@
eb.setBus(readBus());
break;
case "events":
- int[] injections = readIntList().stream()
- .mapToInt(Integer::intValue).toArray();
+ int[] injections = readInjectedEvents().stream()
+ .mapToInt(Integer::intValue).toArray();
eb.setInjections(injections);
break;
case "configuration":
@@ -293,12 +324,17 @@
return e;
}
- private ArrayList<Integer> readIntList() throws IOException {
- ArrayList<Integer> data = new ArrayList<Integer>();
+ private ArrayList<Integer> readInjectedEvents() throws IOException {
+ ArrayList<Integer> data = new ArrayList<>();
try {
mReader.beginArray();
while (mReader.hasNext()) {
- data.add(Integer.decode(mReader.nextString()));
+ // 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) {
@@ -309,22 +345,32 @@
return data;
}
- private byte[] readData() throws IOException {
- ArrayList<Integer> data = readIntList();
- byte[] rawData = new byte[data.size()];
- for (int i = 0; i < data.size(); i++) {
- int d = data.get(i);
- if ((d & 0xFF) != d) {
- throw new IllegalStateException("Invalid data, all values must be byte-sized");
+ private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
+ switch (mReader.peek()) {
+ case NUMBER: {
+ return mReader.nextInt();
}
- rawData[i] = (byte) d;
+ 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.");
+ }
}
- return rawData;
}
private int readInt() throws IOException {
- String val = mReader.nextString();
- return Integer.decode(val);
+ return readValueAsInt((str) -> {
+ throw new IllegalStateException("Encountered malformed data. Expected int.");
+ });
}
private Bus readBus() throws IOException {
@@ -338,17 +384,20 @@
try {
mReader.beginArray();
while (mReader.hasNext()) {
- int type = 0;
+ UinputControlCode controlCode = null;
IntStream data = null;
mReader.beginObject();
while (mReader.hasNext()) {
String name = mReader.nextName();
switch (name) {
case "type":
- type = readInt();
+ controlCode = readUinputControlCode();
break;
case "data":
- data = readIntList().stream().mapToInt(Integer::intValue);
+ Objects.requireNonNull(controlCode,
+ "Configuration 'type' must be specified before 'data'.");
+ data = readDataForControlCode(controlCode)
+ .stream().mapToInt(Integer::intValue);
break;
default:
consumeRemainingElements();
@@ -358,9 +407,9 @@
}
}
mReader.endObject();
- if (data != null) {
- final int[] existing = configuration.get(type);
- configuration.put(type, existing == null ? data.toArray()
+ 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());
}
}
@@ -373,6 +422,60 @@
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 {
@@ -426,7 +529,7 @@
String name = mReader.nextName();
switch (name) {
case "code":
- type = readInt();
+ type = readEvdevEventCode(EV_ABS);
break;
case "info":
absInfo = readAbsInfo();
@@ -452,6 +555,18 @@
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();
diff --git a/core/api/current.txt b/core/api/current.txt
index df8f581..f5db575 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26453,7 +26453,7 @@
method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public void onClose();
- method public void onDeviceStatusChanged(@Nullable android.media.midi.MidiDeviceStatus);
+ method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index c1f6219..cda9ae3 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,6 +6,7 @@
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
+ field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
}
}
@@ -80,6 +81,29 @@
}
+package android.companion {
+
+ public final class CompanionDeviceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+ field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+ field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+ field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+ }
+
+ public static interface CompanionDeviceManager.OnMessageReceivedListener {
+ method public void onMessageReceived(int, @NonNull byte[]);
+ }
+
+ public static interface CompanionDeviceManager.OnTransportsChangedListener {
+ method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
+ }
+
+}
+
package android.content {
public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
@@ -95,6 +119,7 @@
method @NonNull public android.os.IBinder getProcessToken();
method @NonNull public android.os.UserHandle getUser();
field public static final String PAC_PROXY_SERVICE = "pac_proxy";
+ field public static final String REMOTE_AUTH_SERVICE = "remote_auth";
field public static final String TEST_NETWORK_SERVICE = "test_network";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 77168e3..0185080 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -647,6 +647,7 @@
field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
+ field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio";
field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3fb9fd9..833d9c6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -839,6 +839,7 @@
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
+ field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
}
public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 58c2548..a09d7dc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -203,6 +203,7 @@
import android.window.SizeConfigurationBuckets;
import android.window.SplashScreen;
import android.window.SplashScreenView;
+import android.window.WindowContextInfo;
import android.window.WindowProviderService;
import android.window.WindowTokenClientController;
@@ -6248,10 +6249,9 @@
}
@Override
- public void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
- @NonNull Configuration configuration, int displayId) {
- WindowTokenClientController.getInstance().onWindowContextConfigurationChanged(clientToken,
- configuration, displayId);
+ public void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
+ @NonNull WindowContextInfo info) {
+ WindowTokenClientController.getInstance().onWindowContextInfoChanged(clientToken, info);
}
@Override
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bcd43ea..096ad55 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2256,6 +2256,7 @@
*
* @hide
*/
+ @SystemApi
public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
"android:receive_sandbox_trigger_audio";
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f7a43f4..6753cb8 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
import android.util.MergedConfiguration;
import android.view.SurfaceControl;
import android.window.SplashScreenView.SplashScreenViewParcelable;
+import android.window.WindowContextInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
@@ -163,9 +164,9 @@
public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
Configuration overrideConfig, int displayId);
- /** Deliver {@link android.window.WindowContext} configuration change. */
- public abstract void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
- @NonNull Configuration configuration, int displayId);
+ /** Deliver {@link android.window.WindowContextInfo} change. */
+ public abstract void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
+ @NonNull WindowContextInfo info);
/** Deliver {@link android.window.WindowContext} window removal event. */
public abstract void handleWindowContextWindowRemoval(@NonNull IBinder clientToken);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 723c564..5d7993d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2929,19 +2929,16 @@
ComponentName cn = null;
String[] cmfWallpaperMap = context.getResources().getStringArray(
com.android.internal.R.array.default_wallpaper_component_per_device_color);
- if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
- Log.d(TAG, "No CMF wallpaper config");
- return getDefaultWallpaperComponent(context);
- }
-
- for (String entry : cmfWallpaperMap) {
- String[] cmfWallpaper;
- if (!TextUtils.isEmpty(entry)) {
- cmfWallpaper = entry.split(",");
- if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
- cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
- cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
- break;
+ if (cmfWallpaperMap != null && cmfWallpaperMap.length > 0) {
+ for (String entry : cmfWallpaperMap) {
+ String[] cmfWallpaper;
+ if (!TextUtils.isEmpty(entry)) {
+ cmfWallpaper = entry.split(",");
+ if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
+ cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
+ cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
+ break;
+ }
}
}
}
@@ -2950,7 +2947,7 @@
cn = null;
}
- return cn;
+ return cn == null ? getDefaultWallpaperComponent(context) : cn;
}
private static boolean isComponentExist(Context context, ComponentName cn) {
diff --git a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java
deleted file mode 100644
index 3ac642f..0000000
--- a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java
+++ /dev/null
@@ -1,135 +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.app.servertransaction;
-
-import static android.view.Display.INVALID_DISPLAY;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ClientTransactionHandler;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.os.Parcel;
-
-import java.util.Objects;
-
-/**
- * {@link android.window.WindowContext} configuration change message.
- * @hide
- */
-public class WindowContextConfigurationChangeItem extends ClientTransactionItem {
-
- @Nullable
- private IBinder mClientToken;
- @Nullable
- private Configuration mConfiguration;
- private int mDisplayId;
-
- @Override
- public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
- @NonNull PendingTransactionActions pendingActions) {
- client.handleWindowContextConfigurationChanged(mClientToken, mConfiguration, mDisplayId);
- }
-
- // ObjectPoolItem implementation
-
- private WindowContextConfigurationChangeItem() {}
-
- /** Obtains an instance initialized with provided params. */
- public static WindowContextConfigurationChangeItem obtain(
- @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
- WindowContextConfigurationChangeItem instance =
- ObjectPool.obtain(WindowContextConfigurationChangeItem.class);
- if (instance == null) {
- instance = new WindowContextConfigurationChangeItem();
- }
- instance.mClientToken = requireNonNull(clientToken);
- instance.mConfiguration = requireNonNull(config);
- instance.mDisplayId = displayId;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- mClientToken = null;
- mConfiguration = null;
- mDisplayId = INVALID_DISPLAY;
- ObjectPool.recycle(this);
- }
-
- // Parcelable implementation
-
- /** Writes to Parcel. */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeStrongBinder(mClientToken);
- dest.writeTypedObject(mConfiguration, flags);
- dest.writeInt(mDisplayId);
- }
-
- /** Reads from Parcel. */
- private WindowContextConfigurationChangeItem(@NonNull Parcel in) {
- mClientToken = in.readStrongBinder();
- mConfiguration = in.readTypedObject(Configuration.CREATOR);
- mDisplayId = in.readInt();
- }
-
- public static final @NonNull Creator<WindowContextConfigurationChangeItem> CREATOR =
- new Creator<>() {
- public WindowContextConfigurationChangeItem createFromParcel(Parcel in) {
- return new WindowContextConfigurationChangeItem(in);
- }
-
- public WindowContextConfigurationChangeItem[] newArray(int size) {
- return new WindowContextConfigurationChangeItem[size];
- }
- };
-
- @Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- final WindowContextConfigurationChangeItem other = (WindowContextConfigurationChangeItem) o;
- return Objects.equals(mClientToken, other.mClientToken)
- && Objects.equals(mConfiguration, other.mConfiguration)
- && mDisplayId == other.mDisplayId;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + Objects.hashCode(mClientToken);
- result = 31 * result + Objects.hashCode(mConfiguration);
- result = 31 * result + mDisplayId;
- return result;
- }
-
- @Override
- public String toString() {
- return "WindowContextConfigurationChangeItem{clientToken=" + mClientToken
- + ", config=" + mConfiguration
- + ", displayId=" + mDisplayId
- + "}";
- }
-}
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
new file mode 100644
index 0000000..74721d5
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.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 android.app.servertransaction;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.window.WindowContextInfo;
+
+import java.util.Objects;
+
+/**
+ * {@link android.window.WindowContext} configuration change message.
+ * @hide
+ */
+public class WindowContextInfoChangeItem extends ClientTransactionItem {
+
+ @Nullable
+ private IBinder mClientToken;
+ @Nullable
+ private WindowContextInfo mInfo;
+
+ @Override
+ public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+ @NonNull PendingTransactionActions pendingActions) {
+ client.handleWindowContextInfoChanged(mClientToken, mInfo);
+ }
+
+ // ObjectPoolItem implementation
+
+ private WindowContextInfoChangeItem() {}
+
+ /** Obtains an instance initialized with provided params. */
+ public static WindowContextInfoChangeItem obtain(
+ @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
+ WindowContextInfoChangeItem instance =
+ ObjectPool.obtain(WindowContextInfoChangeItem.class);
+ if (instance == null) {
+ instance = new WindowContextInfoChangeItem();
+ }
+ instance.mClientToken = requireNonNull(clientToken);
+ instance.mInfo = new WindowContextInfo(config, displayId);
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ mClientToken = null;
+ mInfo = null;
+ ObjectPool.recycle(this);
+ }
+
+ // Parcelable implementation
+
+ /** Writes to Parcel. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStrongBinder(mClientToken);
+ dest.writeTypedObject(mInfo, flags);
+ }
+
+ /** Reads from Parcel. */
+ private WindowContextInfoChangeItem(@NonNull Parcel in) {
+ mClientToken = in.readStrongBinder();
+ mInfo = in.readTypedObject(WindowContextInfo.CREATOR);
+ }
+
+ public static final @NonNull Creator<WindowContextInfoChangeItem> CREATOR =
+ new Creator<>() {
+ public WindowContextInfoChangeItem createFromParcel(Parcel in) {
+ return new WindowContextInfoChangeItem(in);
+ }
+
+ public WindowContextInfoChangeItem[] newArray(int size) {
+ return new WindowContextInfoChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowContextInfoChangeItem other = (WindowContextInfoChangeItem) o;
+ return Objects.equals(mClientToken, other.mClientToken)
+ && Objects.equals(mInfo, other.mInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mClientToken);
+ result = 31 * result + Objects.hashCode(mInfo);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowContextInfoChangeItem{clientToken=" + mClientToken
+ + ", info=" + mInfo
+ + "}";
+ }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index e45fa19..69e1653 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -20,7 +20,9 @@
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -209,6 +211,34 @@
public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
/**
+ * Test message type without a designated callback.
+ *
+ * @hide
+ */
+ @TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+ /**
+ * Message header assigned to the remote authentication handshakes.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
+ /**
+ * Message header assigned to the telecom context sync metadata.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
+ /**
+ * Message header assigned to the permission restore request.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+ /**
* Callback for applications to receive updates about and the outcome of
* {@link AssociationRequest} issued via {@code associate()} call.
*
@@ -797,28 +827,36 @@
}
/**
- * Listener for any changes to {@link com.android.server.companion.transport.Transport}.
+ * Listener for any changes to the list of attached transports.
+ *
+ * @see com.android.server.companion.transport.Transport
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public interface OnTransportsChangedListener {
/**
- * Invoked when a change occurs to any of the transports
+ * Invoked when a transport is attached or detached.
*
- * @param associations all the associations which have connected transports
+ * @param associations all the associations which have connected transports.
*/
void onTransportsChanged(@NonNull List<AssociationInfo> associations);
}
/**
- * Register a listener for any changes to
- * {@link com.android.server.companion.transport.Transport}. Your app will receive a callback to
- * {@link OnTransportsChangedListener} immediately with all the existing transports.
+ * Adds a listener for any changes to the list of attached transports.
+ * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
+ * of existing transports when a transport is detached or a new transport is attached.
+ *
+ * @see com.android.server.companion.transport.Transport
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(
- @NonNull Executor executor, @NonNull OnTransportsChangedListener listener) {
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnTransportsChangedListener listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
executor, listener);
try {
@@ -829,11 +867,14 @@
}
/**
- * Unregister a listener to stop receiving any changes to
- * {@link com.android.server.companion.transport.Transport}.
+ * Removes the registered listener for any changes to the list of attached transports.
+ *
+ * @see com.android.server.companion.transport.Transport
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
@NonNull OnTransportsChangedListener listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
@@ -846,11 +887,16 @@
}
/**
- * Send a message to remote devices
+ * Sends a message to associated remote devices. The target associations must already have a
+ * connected transport.
+ *
+ * @see #attachSystemDataTransport(int, InputStream, OutputStream)
*
* @hide
*/
- public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
+ public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
try {
mService.sendMessage(messageType, data, associationIds);
} catch (RemoteException e) {
@@ -859,28 +905,30 @@
}
/**
- * Listener when a message is received for the registered message type
+ * Listener that triggers a callback when a message is received through a connected transport.
*
* @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public interface OnMessageReceivedListener {
/**
- * Called when a message is received
+ * Called when a message is received.
*/
- void onMessageReceived(int associationId, byte[] data);
+ void onMessageReceived(int associationId, @NonNull byte[] data);
}
/**
- * Register a listener to receive callbacks when a message is received by the given type
- *
- * @see com.android.server.companion.transport.Transport for supported message types
+ * Adds a listener to trigger callbacks when messages of given type are received.
*
* @hide
*/
- public void addOnMessageReceivedListener(@NonNull Executor executor, int messageType,
- OnMessageReceivedListener listener) {
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
+ public void addOnMessageReceivedListener(
+ @NonNull @CallbackExecutor Executor executor, int messageType,
+ @NonNull OnMessageReceivedListener listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
executor, listener);
try {
@@ -891,15 +939,14 @@
}
/**
- * Unregister a listener to stop receiving callbacks when a message is received by the given
- * type
- *
- * @see com.android.server.companion.transport.Transport for supported message types
+ * Removes the registered listener for received messages of given type.
*
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
- OnMessageReceivedListener listener) {
+ @NonNull OnMessageReceivedListener listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
null, listener);
try {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 2318e30..463dba2 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -74,14 +74,19 @@
@EnforcePermission("MANAGE_COMPANION_DEVICES")
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+ @EnforcePermission("USE_COMPANION_TRANSPORTS")
void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
+ @EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnTransportsChangedListener(IOnTransportsChangedListener listener);
+ @EnforcePermission("USE_COMPANION_TRANSPORTS")
void sendMessage(int messageType, in byte[] data, in int[] associationIds);
+ @EnforcePermission("USE_COMPANION_TRANSPORTS")
void addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
+ @EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
void notifyDeviceAppeared(int associationId);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b2cd7e9..9253998 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1985,7 +1985,7 @@
* Open a raw file descriptor to access (potentially type transformed)
* data from a "content:" URI. This interacts with the underlying
* {@link ContentProvider#openTypedAssetFile} method of the provider
- * associated with the given URI, to retrieve retrieve any appropriate
+ * associated with the given URI, to retrieve any appropriate
* data stream for the data stored there.
*
* <p>Unlike {@link #openAssetFileDescriptor}, this function only works
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a6d84b..c11a8fc 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6341,6 +6341,18 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.remoteauth.RemoteAuthManager} to discover,
+ * register and authenticate via remote authenticator devices.
+ *
+ * @see #getSystemService(String)
+ * @see android.remoteauth.RemoteAuthManager
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String REMOTE_AUTH_SERVICE = "remote_auth";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.app.ambientcontext.AmbientContextManager}.
*
* @see #getSystemService(String)
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 3e0ab90..a86c0c9 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.util.Pools.SimplePool;
+import android.util.Slog;
import androidx.annotation.StyleableRes;
@@ -41,6 +42,7 @@
public static final int MAX_ATTR_LEN_PATH = 4000;
public static final int MAX_ATTR_LEN_DATA_VALUE = 4000;
+ private static final String TAG = "PackageParsing";
protected static final String TAG_ACTION = "action";
protected static final String TAG_ACTIVITY = "activity";
protected static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
@@ -715,23 +717,123 @@
mChildTagMask |= 1 << idx;
}
+ private boolean isComponentNameAttr(String name) {
+ switch (mTag) {
+ case TAG_ACTIVITY:
+ switch (name) {
+ case TAG_ATTR_NAME:
+ case TAG_ATTR_PARENT_ACTIVITY_NAME:
+ return true;
+ default:
+ return false;
+ }
+ case TAG_ACTIVITY_ALIAS:
+ switch (name) {
+ case TAG_ATTR_TARGET_ACTIVITY:
+ return true;
+ default:
+ return false;
+ }
+ case TAG_APPLICATION:
+ switch (name) {
+ case TAG_ATTR_BACKUP_AGENT:
+ case TAG_ATTR_NAME:
+ return true;
+ default:
+ return false;
+ }
+ case TAG_INSTRUMENTATION:
+ case TAG_PROVIDER:
+ case TAG_RECEIVER:
+ case TAG_SERVICE:
+ switch (name) {
+ case TAG_ATTR_NAME:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+ }
+
+ private boolean isComponentNameAttr(@StyleableRes int index) {
+ switch (mTag) {
+ case TAG_ACTIVITY:
+ return index == R.styleable.AndroidManifestActivity_name
+ || index == R.styleable.AndroidManifestActivity_parentActivityName;
+ case TAG_ACTIVITY_ALIAS:
+ return index == R.styleable.AndroidManifestActivityAlias_targetActivity;
+ case TAG_APPLICATION:
+ return index == R.styleable.AndroidManifestApplication_backupAgent
+ || index == R.styleable.AndroidManifestApplication_name;
+ case TAG_INSTRUMENTATION:
+ return index == R.styleable.AndroidManifestInstrumentation_name;
+ case TAG_PROVIDER:
+ return index == R.styleable.AndroidManifestProvider_name;
+ case TAG_RECEIVER:
+ return index == R.styleable.AndroidManifestReceiver_name;
+ case TAG_SERVICE:
+ return index == R.styleable.AndroidManifestService_name;
+ default:
+ return false;
+ }
+ }
+
boolean hasChild(String tag) {
return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
}
+ void validateComponentName(CharSequence name) {
+ int i = 0;
+ if (name.charAt(0) == '.') {
+ i = 1;
+ }
+ boolean isStart = true;
+ for (; i < name.length(); i++) {
+ if (name.charAt(i) == '.') {
+ if (isStart) {
+ break;
+ }
+ isStart = true;
+ } else {
+ if (isStart) {
+ if (Character.isJavaIdentifierStart(name.charAt(i))) {
+ isStart = false;
+ } else {
+ break;
+ }
+ } else if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+ break;
+ }
+ }
+ }
+ if ((i < name.length()) || (name.charAt(name.length() - 1) == '.')) {
+ Slog.e(TAG, name + " is not a valid Java class name");
+ throw new SecurityException(name + " is not a valid Java class name");
+ }
+ }
+
void validateStrAttr(String attrName, String attrValue) {
if (attrValue != null && attrValue.length() > getAttrStrMaxLen(attrName)) {
throw new SecurityException("String length limit exceeded for attribute " + attrName
+ " in " + mTag);
}
+ if (isComponentNameAttr(attrName)) {
+ validateComponentName(attrValue);
+ }
}
void validateResStrAttr(@StyleableRes int index, CharSequence stringValue) {
if (stringValue != null && stringValue.length() > getResStrMaxLen(index)) {
throw new SecurityException("String length limit exceeded for attribute in " + mTag);
}
+ if (isComponentNameAttr(index)) {
+ validateComponentName(stringValue);
+ }
}
+
void seen(@NonNull Element element) {
TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
if (counter != null) {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3a66081..c872516 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3535,7 +3535,7 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
* They can be queried through
* {@link android.hardware.camera2.CameraCharacteristics#get } with
- * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
* Unless reported by both
* {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
* <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -3550,13 +3550,12 @@
* <ul>
* <li>
* <p>The mandatory stream combinations listed in
- * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
- * would not apply.</p>
+ * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p>
* </li>
* <li>
* <p>The bayer pattern of {@code RAW} streams when
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+ * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
* </li>
* <li>
* <p>The following keys will always be present:</p>
@@ -3576,9 +3575,11 @@
*
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
+ * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1536376..57f7bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4460,7 +4460,7 @@
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
* They can be queried through
* {@link android.hardware.camera2.CameraCharacteristics#get } with
- * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+ * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
* Unless reported by both
* {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
* <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -4475,13 +4475,12 @@
* <ul>
* <li>
* <p>The mandatory stream combinations listed in
- * {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
- * would not apply.</p>
+ * {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations} would not apply.</p>
* </li>
* <li>
* <p>The bayer pattern of {@code RAW} streams when
* {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
- * is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+ * is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
* </li>
* <li>
* <p>The following keys will always be present:</p>
@@ -4501,9 +4500,11 @@
*
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*
+ * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+ * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
* @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index ae700a0..e06699b 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -266,7 +266,7 @@
List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
ArrayList<OutputConfiguration> outputList = new ArrayList<>();
for (CameraOutputConfig output : outputConfigs) {
- Surface outputSurface = initializeSurfrace(output);
+ Surface outputSurface = initializeSurface(output);
if (outputSurface == null) {
continue;
}
@@ -279,7 +279,7 @@
if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
cameraOutput.enableSurfaceSharing();
for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
- Surface sharedSurface = initializeSurfrace(sharedOutputConfig);
+ Surface sharedSurface = initializeSurface(sharedOutputConfig);
if (sharedSurface == null) {
continue;
}
@@ -1159,7 +1159,7 @@
return ret;
}
- private Surface initializeSurfrace(CameraOutputConfig output) {
+ private Surface initializeSurface(CameraOutputConfig output) {
switch(output.type) {
case CameraOutputConfig.TYPE_SURFACE:
if (output.surface == null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1db4808..5d25681 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -2110,10 +2110,10 @@
HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap, Integer jpegOrientation,
Byte jpegQuality) {
ArrayList<CaptureBundle> ret = new ArrayList<>();
- for (Integer stagetId : captureMap.keySet()) {
- Pair<Image, TotalCaptureResult> entry = captureMap.get(stagetId);
+ for (Integer stageId : captureMap.keySet()) {
+ Pair<Image, TotalCaptureResult> entry = captureMap.get(stageId);
CaptureBundle bundle = new CaptureBundle();
- bundle.stage = stagetId;
+ bundle.stage = stageId;
bundle.captureImage = initializeParcelImage(entry.first);
bundle.sequenceId = entry.second.getSequenceId();
bundle.captureResult = entry.second.getNativeMetadata();
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7b5dd55..795eb4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1019,8 +1019,9 @@
if (!mOnPreparedStylusHwCalled) {
// prepare hasn't been called by Stylus HOVER.
onPrepareStylusHandwriting();
- mOnPreparedStylusHwCalled = true;
}
+ // reset flag as it's not relevant after onStartStylusHandwriting().
+ mOnPreparedStylusHwCalled = false;
if (onStartStylusHandwriting()) {
cancelStylusWindowIdleTimeout();
mPrivOps.onStylusHandwritingReady(requestId, Process.myPid());
@@ -3089,7 +3090,8 @@
mInputStarted = false;
mStartedInputConnection = null;
mCurCompletions = null;
- if (mInkWindow != null) {
+ if (!mOnPreparedStylusHwCalled) {
+ // If IME didn't prepare to show InkWindow for current handwriting session.
finishStylusHandwriting();
}
// Back callback is typically unregistered in {@link #hideWindow()}, but it's possible
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index db1a741..f32a1f8 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -24,7 +24,9 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* A CombinedVibration describes a combination of haptic effects to be performed by one or more
@@ -151,6 +153,13 @@
public abstract boolean hasVibrator(int vibratorId);
/**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
+ /**
* Adapts a {@link VibrationEffect} to a specific device vibrator using the ID.
*
* <p>This can be used for adapting effects to the capabilities of the specific device vibrator
@@ -437,6 +446,13 @@
return "Mono{mEffect=" + mEffect + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ // Simplify vibration string, use the single effect to represent it.
+ return mEffect.toDebugString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -619,6 +635,17 @@
return "Stereo{mEffects=" + mEffects + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ StringJoiner sj = new StringJoiner(",", "Stereo{", "}");
+ for (int i = 0; i < mEffects.size(); i++) {
+ sj.add(String.format(Locale.ROOT, "vibrator(id=%d): %s",
+ mEffects.keyAt(i), mEffects.valueAt(i).toDebugString()));
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -833,6 +860,17 @@
return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ StringJoiner sj = new StringJoiner(",", "Sequential{", "}");
+ for (int i = 0; i < mEffects.size(); i++) {
+ sj.add(String.format(Locale.ROOT, "delayMs=%d, effect=%s",
+ mDelays.get(i), mEffects.get(i).toDebugString()));
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 299e7f1..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,7 +115,6 @@
private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
- private static final String SYSTEM_ANGLE_STRING = "system";
private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
@@ -196,16 +195,15 @@
}
/**
- * Query to determine the ANGLE driver choice.
+ * Query to determine if ANGLE should be used
*/
- private String queryAngleChoice(Context context, Bundle coreSettings,
- String packageName) {
+ private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
if (TextUtils.isEmpty(packageName)) {
Log.v(TAG, "No package name specified; use the system driver");
- return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+ return false;
}
- return queryAngleChoiceInternal(context, coreSettings, packageName);
+ return shouldUseAngleInternal(context, coreSettings, packageName);
}
private int getVulkanVersion(PackageManager pm) {
@@ -426,11 +424,10 @@
* forces a choice;
* 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
*/
- private String queryAngleChoiceInternal(Context context, Bundle bundle,
- String packageName) {
+ private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
// Make sure we have a good package name
if (TextUtils.isEmpty(packageName)) {
- return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+ return false;
}
// Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -445,7 +442,7 @@
}
if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
Log.v(TAG, "Turn on ANGLE for all applications.");
- return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ return true;
}
// Get the per-application settings lists
@@ -468,8 +465,7 @@
+ optInPackages.size() + ", "
+ "number of values: "
+ optInValues.size());
- return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
- : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+ return mEnabledByGameMode;
}
// See if this application is listed in the per-application settings list
@@ -477,8 +473,7 @@
if (pkgIndex < 0) {
Log.v(TAG, packageName + " is not listed in per-application setting");
- return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
- : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+ return mEnabledByGameMode;
}
mAngleOptInIndex = pkgIndex;
@@ -489,14 +484,13 @@
"ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + optInValue + "'");
if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
- return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ return true;
} else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
- return ANGLE_GL_DRIVER_CHOICE_NATIVE;
+ return false;
} else {
// The user either chose default or an invalid value; go with the default driver or what
// the game mode indicates
- return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
- : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+ return mEnabledByGameMode;
}
}
@@ -563,12 +557,8 @@
*/
private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
String packageName) {
- final String angleChoice = queryAngleChoice(context, bundle, packageName);
- if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
- return false;
- }
- if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
- nativeSetAngleInfo("", true, packageName, null);
+
+ if (!shouldUseAngle(context, bundle, packageName)) {
return false;
}
@@ -637,10 +627,10 @@
Log.d(TAG, "ANGLE package libs: " + paths);
}
- // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the
- // application package name and ANGLE features to use.
+ // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
+ // and features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- nativeSetAngleInfo(paths, false, packageName, features);
+ setAngleInfo(paths, false, packageName, features);
return true;
}
@@ -662,10 +652,10 @@
return false;
}
- // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with
- // the application package name and ANGLE features to use.
+ // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name,
+ // and features to use.
final String[] features = getAngleEglFeatures(context, bundle);
- nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
+ setAngleInfo("", true, packageName, features);
return true;
}
@@ -946,8 +936,8 @@
private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
- private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
- String packageName, String[] features);
+ private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+ String[] features);
private static native boolean setInjectLayersPrSetDumpable();
private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index f79d6e6..977ef60 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -29,6 +29,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.Reference;
+import java.util.Objects;
/** The PerformanceHintManager allows apps to send performance hint to system. */
@@ -239,6 +240,7 @@
if (mNativeSessionPtr == 0) {
return;
}
+ Objects.requireNonNull(tids, "tids cannot be null");
if (tids.length == 0) {
throw new IllegalArgumentException("Thread id list can't be empty.");
}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 954ee3c..dae9b5e 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -27,6 +27,19 @@
]
},
{
+ "file_patterns": [
+ "[^/]*(Vibrator|Vibration)[^/]*\\.java",
+ "vibrator/.*"
+ ],
+ "name": "CtsVibratorTestCases",
+ "options": [
+ {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ },
+ {
"file_patterns": ["Bugreport[^/]*\\.java"],
"name": "BugreportManagerTestCases",
"options": [
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index b2d62eb..98f9dff 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -336,10 +336,11 @@
@Override
public String toString() {
- return "VibrationAttributes:"
- + " Usage=" + usageToString()
- + " Audio Usage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
- + " Flags=" + mFlags;
+ return "VibrationAttributes{"
+ + "mUsage=" + usageToString()
+ + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+ + ", mFlags=" + mFlags
+ + '}';
}
/** @hide */
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0461b2e..b24b45d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -44,7 +44,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -232,11 +234,11 @@
* Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
* vibration components) that is equivalent to this VibrationEffect.
*
- * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
- * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
- * created with other means becomes converted into an equivalent legacy vibration pattern, even
- * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
- * vibration pattern for such effects, it will return {@code null}.
+ * <p>All non-repeating effects created with {@link #createWaveform(long[], int)} are
+ * convertible into an equivalent vibration pattern with this method. It is not guaranteed that
+ * an effect created with other means becomes converted into an equivalent legacy vibration
+ * pattern, even if it has an equivalent vibration pattern. If this method is unable to create
+ * an equivalent vibration pattern for such effects, it will return {@code null}.
*
* <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
* form of repeating behavior, regardless of how the effect was created. For repeating effects,
@@ -245,7 +247,7 @@
* @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
* the method successfully derived a vibration pattern equivalent to the effect
* (this will always be the case if the effect was created via
- * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+ * {@link #createWaveform(long[], int)} and is non-repeating). Otherwise, returns
* {@code null}.
* @hide
*/
@@ -629,6 +631,13 @@
return MathUtils.constrain(a * fx, 0f, 1f);
}
+ /**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
/** @hide */
public static String effectIdToString(int effectId) {
switch (effectId) {
@@ -925,6 +934,23 @@
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ if (mSegments.size() == 1 && mRepeatIndex < 0) {
+ // Simplify effect string, use the single segment to represent it.
+ return mSegments.get(0).toDebugString();
+ }
+ StringJoiner sj = new StringJoiner(",", "[", "]");
+ for (int i = 0; i < mSegments.size(); i++) {
+ sj.add(mSegments.get(i).toDebugString());
+ }
+ if (mRepeatIndex >= 0) {
+ return String.format(Locale.ROOT, "%s, repeat=%d", sj, mRepeatIndex);
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -953,7 +979,7 @@
/**
* Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
* only if it can possibly be a segment for an effect created via
- * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+ * {@link #createWaveform(long[], int)}. Otherwise, returns {@code null}.
*/
@Nullable
private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
@@ -1250,23 +1276,23 @@
public static String primitiveToString(@PrimitiveType int id) {
switch (id) {
case PRIMITIVE_NOOP:
- return "PRIMITIVE_NOOP";
+ return "NOOP";
case PRIMITIVE_CLICK:
- return "PRIMITIVE_CLICK";
+ return "CLICK";
case PRIMITIVE_THUD:
- return "PRIMITIVE_THUD";
+ return "THUD";
case PRIMITIVE_SPIN:
- return "PRIMITIVE_SPIN";
+ return "SPIN";
case PRIMITIVE_QUICK_RISE:
- return "PRIMITIVE_QUICK_RISE";
+ return "QUICK_RISE";
case PRIMITIVE_SLOW_RISE:
- return "PRIMITIVE_SLOW_RISE";
+ return "SLOW_RISE";
case PRIMITIVE_QUICK_FALL:
- return "PRIMITIVE_QUICK_FALL";
+ return "QUICK_FALL";
case PRIMITIVE_TICK:
- return "PRIMITIVE_TICK";
+ return "TICK";
case PRIMITIVE_LOW_TICK:
- return "PRIMITIVE_LOW_TICK";
+ return "LOW_TICK";
default:
return Integer.toString(id);
}
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 71ec096..02e6856 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.util.Range;
import android.util.SparseBooleanArray;
@@ -207,6 +208,25 @@
+ '}';
}
+ /** @hide */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VibratorInfo:");
+ pw.increaseIndent();
+ pw.println("id = " + mId);
+ pw.println("capabilities = " + Arrays.toString(getCapabilitiesNames()));
+ pw.println("capabilitiesFlags = " + Long.toBinaryString(mCapabilities));
+ pw.println("supportedEffects = " + Arrays.toString(getSupportedEffectsNames()));
+ pw.println("supportedPrimitives = " + Arrays.toString(getSupportedPrimitivesNames()));
+ pw.println("supportedBraking = " + Arrays.toString(getSupportedBrakingNames()));
+ pw.println("primitiveDelayMax = " + mPrimitiveDelayMax);
+ pw.println("compositionSizeMax = " + mCompositionSizeMax);
+ pw.println("pwlePrimitiveDurationMax = " + mPwlePrimitiveDurationMax);
+ pw.println("pwleSizeMax = " + mPwleSizeMax);
+ pw.println("q-factor = " + mQFactor);
+ pw.println("frequencyProfile = " + mFrequencyProfile);
+ pw.decreaseIndent();
+ }
+
/** Return the id of this vibrator. */
public int getId() {
return mId;
@@ -370,7 +390,7 @@
* Gets the resonant frequency of the vibrator.
*
* @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
+ * this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequencyHz() {
return mFrequencyProfile.mResonantFrequencyHz;
@@ -380,7 +400,7 @@
* Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
*
* @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
+ * this vibrator is a composite of multiple physical devices.
*/
public float getQFactor() {
return mQFactor;
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index da2ee6c..42b6c2da 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -119,12 +119,6 @@
}
/** @hide */
- @Override
- public boolean hasNonZeroAmplitude() {
- return true;
- }
-
- /** @hide */
@NonNull
@Override
public PrebakedSegment resolve(int defaultAmplitude) {
@@ -209,6 +203,15 @@
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Prebaked=%s(%s, %s fallback)",
+ VibrationEffect.effectIdToString(mEffectId),
+ VibrationEffect.effectStrengthToString(mEffectStrength),
+ mFallback ? "with" : "no");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index e1fa97b..c52a09c 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -88,13 +88,6 @@
}
/** @hide */
- @Override
- public boolean hasNonZeroAmplitude() {
- // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
- return true;
- }
-
- /** @hide */
@NonNull
@Override
public PrimitiveSegment resolve(int defaultAmplitude) {
@@ -147,6 +140,13 @@
+ '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Primitive=%s(scale=%.2f, delay=%dms)",
+ VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, mDelay);
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 034962a..e997bcd 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -125,12 +125,6 @@
/** @hide */
@Override
- public boolean hasNonZeroAmplitude() {
- return mStartAmplitude > 0 || mEndAmplitude > 0;
- }
-
- /** @hide */
- @Override
public void validate() {
VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
@@ -185,6 +179,17 @@
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Ramp=%dms(amplitude=%.2f%s to %.2f%s)",
+ mDuration,
+ mStartAmplitude,
+ Float.compare(mStartFrequencyHz, 0) == 0 ? "" : " @ " + mStartFrequencyHz + "Hz",
+ mEndAmplitude,
+ Float.compare(mEndFrequencyHz, 0) == 0 ? "" : " @ " + mEndFrequencyHz + "Hz");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 817187e..a585aa8 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -101,13 +101,6 @@
/** @hide */
@Override
- public boolean hasNonZeroAmplitude() {
- // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
- return Float.compare(mAmplitude, 0) != 0;
- }
-
- /** @hide */
- @Override
public void validate() {
VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
@@ -168,6 +161,13 @@
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Step=%dms(amplitude=%.2f%s)", mDuration, mAmplitude,
+ Float.compare(mFrequencyHz, 0) == 0 ? "" : " @ " + mFrequencyHz + "Hz");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 4790d81..bde334a 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -32,6 +32,9 @@
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
/**
* List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -191,4 +194,18 @@
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ "}";
}
+
+ /**
+ * Write current settings into given {@link PrintWriter}, skipping the default settings.
+ *
+ * @hide
+ */
+ public void dumpWithoutDefaultSettings(IndentingPrintWriter pw) {
+ pw.println("VibrationConfig:");
+ pw.increaseIndent();
+ pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
+ pw.println("rampStepDurationMs = " + mRampStepDurationMs);
+ pw.println("rampDownDurationMs = " + mRampDownDurationMs);
+ pw.decreaseIndent();
+ }
}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 75a055f..3b286a7 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -76,13 +76,6 @@
public abstract boolean isHapticFeedbackCandidate();
/**
- * Returns true if this segment plays at a non-zero amplitude at some point.
- *
- * @hide
- */
- public abstract boolean hasNonZeroAmplitude();
-
- /**
* Validates the segment, throwing exceptions if any parameter is invalid.
*
* @hide
@@ -123,6 +116,13 @@
public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
/**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
+ /**
* Checks the given frequency argument is valid to represent a vibration effect frequency in
* hertz, i.e. a finite non-negative value.
*
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 3c5757d..5d6dfc7 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -63,6 +63,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -1956,9 +1957,8 @@
userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI,
user.getIdentifier());
- if (VERBOSE_LOG) {
- Log.v(LOG_TAG, String.format("Inserting to %s", uri));
- }
+ Log.i(LOG_TAG, String.format(Locale.getDefault(),
+ "addEntryAndRemoveExpiredEntries: provider uri=%s", uri));
try {
// When cleaning up the call log, try to delete older call long entries on a per
diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING
index b26a38b..a3a86fd 100644
--- a/core/java/android/util/apk/TEST_MAPPING
+++ b/core/java/android/util/apk/TEST_MAPPING
@@ -15,7 +15,7 @@
"include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
},
{
- "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallerTest"
+ "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallTest"
}
]
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c1474eb..d3b7a5b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -71,6 +71,7 @@
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ScreenCapture;
+import android.window.WindowContextInfo;
/**
* System private interface to the window manager.
@@ -858,10 +859,10 @@
* @param displayId The display associated with the window context
* @param options A bundle used to pass window-related options and choose the right DisplayArea
*
- * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
- * attached to the DisplayArea successfully. {@code null}, otherwise.
+ * @return the {@link WindowContextInfo} of the DisplayArea if the WindowContext is attached to
+ * the DisplayArea successfully. {@code null}, otherwise.
*/
- @nullable Configuration attachWindowContextToDisplayArea(in IApplicationThread appThread,
+ @nullable WindowContextInfo attachWindowContextToDisplayArea(in IApplicationThread appThread,
IBinder clientToken, int type, int displayId, in @nullable Bundle options);
/**
@@ -879,13 +880,15 @@
* the WindowContext's token}
* @param token the WindowToken to attach
*
+ * @return the {@link WindowContextInfo} of the WindowToken if the WindowContext is attached to
+ * the WindowToken successfully. {@code null}, otherwise.
* @throws IllegalArgumentException if the {@code clientToken} have not been attached to
* the server or the WindowContext's type doesn't match WindowToken {@code token}'s type.
*
* @see #attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
*/
- void attachWindowContextToWindowToken(in IApplicationThread appThread, IBinder clientToken,
- IBinder token);
+ @nullable WindowContextInfo attachWindowContextToWindowToken(in IApplicationThread appThread,
+ IBinder clientToken, IBinder token);
/**
* Attaches a {@code clientToken} to associate with DisplayContent.
@@ -899,11 +902,11 @@
* the WindowContext's token}
* @param displayId The display associated with the window context
*
- * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is
- * attached to the DisplayContent successfully. {@code null}, otherwise.
+ * @return the {@link WindowContextInfo} of the DisplayContent if the WindowContext is attached
+ * to the DisplayContent successfully. {@code null}, otherwise.
* @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid
*/
- @nullable Configuration attachWindowContextToDisplayContent(in IApplicationThread appThread,
+ @nullable WindowContextInfo attachWindowContextToDisplayContent(in IApplicationThread appThread,
IBinder clientToken, int displayId);
/**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 17afd55..4ecfc40 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -844,12 +844,12 @@
public boolean onStateChanged(InsetsState state) {
boolean stateChanged = false;
if (!CAPTION_ON_SHELL) {
- stateChanged = !mState.equals(state, true /* excludingCaptionInsets */,
- false /* excludeInvisibleIme */)
+ stateChanged = !mState.equals(state, true /* excludesCaptionBar */,
+ false /* excludesInvisibleIme */)
|| captionInsetsUnchanged();
} else {
- stateChanged = !mState.equals(state, false /* excludingCaptionInsets */,
- false /* excludeInvisibleIme */);
+ stateChanged = !mState.equals(state, false /* excludesCaptionBar */,
+ false /* excludesInvisibleIme */);
}
if (!stateChanged && mLastDispatchedState.equals(state)) {
return false;
@@ -862,8 +862,8 @@
applyLocalVisibilityOverride();
updateCompatSysUiVisibility();
- if (!mState.equals(lastState, false /* excludingCaptionInsets */,
- true /* excludeInvisibleIme */)) {
+ if (!mState.equals(lastState, false /* excludesCaptionBar */,
+ true /* excludesInvisibleIme */)) {
if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
mHost.notifyInsetsChanged();
if (lastState.getDisplayFrame().equals(mState.getDisplayFrame())) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c13b9ab..af24140 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -380,11 +380,17 @@
@InternalInsetsSide @Nullable SparseIntArray idSideMap,
@Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
int index = indexOf(type);
- Insets existing = typeInsetsMap[index];
- if (existing == null) {
- typeInsetsMap[index] = insets;
- } else {
- typeInsetsMap[index] = Insets.max(existing, insets);
+
+ // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
+ // as non-equal while they provide the same insets of each type from WindowInsets#getInsets
+ // if one WindowInsets has Insets.NONE for a type and the other has null for the same type.
+ if (!Insets.NONE.equals(insets)) {
+ Insets existing = typeInsetsMap[index];
+ if (existing == null) {
+ typeInsetsMap[index] = insets;
+ } else {
+ typeInsetsMap[index] = Insets.max(existing, insets);
+ }
}
if (typeVisibilityMap != null) {
@@ -696,15 +702,14 @@
* An equals method can exclude the caption insets. This is useful because we assemble the
* caption insets information on the client side, and when we communicate with server, it's
* excluded.
- * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
- * ignore the caption insets source value.
- * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
- * when IME is not visible.
+ * @param excludesCaptionBar If {@link Type#captionBar()}} should be ignored.
+ * @param excludesInvisibleIme If {@link WindowInsets.Type#ime()} should be ignored when IME is
+ * not visible.
* @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
*/
@VisibleForTesting
- public boolean equals(@Nullable Object o, boolean excludingCaptionInsets,
- boolean excludeInvisibleImeFrames) {
+ public boolean equals(@Nullable Object o, boolean excludesCaptionBar,
+ boolean excludesInvisibleIme) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
@@ -721,29 +726,35 @@
final SparseArray<InsetsSource> thisSources = mSources;
final SparseArray<InsetsSource> thatSources = state.mSources;
- if (!excludingCaptionInsets && !excludeInvisibleImeFrames) {
+ if (!excludesCaptionBar && !excludesInvisibleIme) {
return thisSources.contentEquals(thatSources);
} else {
final int thisSize = thisSources.size();
final int thatSize = thatSources.size();
int thisIndex = 0;
int thatIndex = 0;
- while (thisIndex < thisSize && thatIndex < thatSize) {
+ while (thisIndex < thisSize || thatIndex < thatSize) {
+ InsetsSource thisSource = thisIndex < thisSize
+ ? thisSources.valueAt(thisIndex)
+ : null;
+
// Seek to the next non-excluding source of ours.
- InsetsSource thisSource = thisSources.valueAt(thisIndex);
while (thisSource != null
- && (excludingCaptionInsets && thisSource.getType() == captionBar()
- || excludeInvisibleImeFrames && thisSource.getType() == ime()
+ && (excludesCaptionBar && thisSource.getType() == captionBar()
+ || excludesInvisibleIme && thisSource.getType() == ime()
&& !thisSource.isVisible())) {
thisIndex++;
thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null;
}
+ InsetsSource thatSource = thatIndex < thatSize
+ ? thatSources.valueAt(thatIndex)
+ : null;
+
// Seek to the next non-excluding source of theirs.
- InsetsSource thatSource = thatSources.valueAt(thatIndex);
while (thatSource != null
- && (excludingCaptionInsets && thatSource.getType() == captionBar()
- || excludeInvisibleImeFrames && thatSource.getType() == ime()
+ && (excludesCaptionBar && thatSource.getType() == captionBar()
+ || excludesInvisibleIme && thatSource.getType() == ime()
&& !thatSource.isVisible())) {
thatIndex++;
thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null;
@@ -756,7 +767,7 @@
thisIndex++;
thatIndex++;
}
- return thisIndex >= thisSize && thatIndex >= thatSize;
+ return true;
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 01a99b9..1b1098d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2539,7 +2539,7 @@
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final float x = event.getXDispatchLocation(0);
- final float y = event.getXDispatchLocation(0);
+ final float y = event.getYDispatchLocation(0);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 6aa8506..d20d95d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -34,7 +34,6 @@
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -165,6 +164,7 @@
}
}
+ @Nullable
@UnsupportedAppUsage
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
@@ -172,6 +172,7 @@
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
+ // Can be null if this is called before WindowManagerService is initialized.
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
@@ -185,15 +186,6 @@
}
}
- /** Overrides the {@link #getWindowManagerService()} for test only. */
- @VisibleForTesting
- public static void overrideWindowManagerServiceForTesting(
- @NonNull IWindowManager windowManager) {
- synchronized (WindowManagerGlobal.class) {
- sWindowManagerService = windowManager;
- }
- }
-
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6ad1960..03364b6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -916,6 +916,8 @@
}
}
+ private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+
public AbsListView(Context context) {
super(context);
setupDeviceConfigProperties();
@@ -4488,17 +4490,22 @@
public boolean onGenericMotionEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
- final float axisValue;
+ final int axis;
if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
- axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ axis = MotionEvent.AXIS_VSCROLL;
} else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
- axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
+ axis = MotionEvent.AXIS_SCROLL;
} else {
- axisValue = 0;
+ axis = -1;
}
+ final float axisValue = (axis == -1) ? 0 : event.getAxisValue(axis);
final int delta = Math.round(axisValue * mVerticalScrollFactor);
if (delta != 0) {
+ // Tracks whether or not we should attempt fling for this event.
+ // Fling should not be attempted if the view is already at the limit of scroll,
+ // since it conflicts with EdgeEffect.
+ boolean shouldAttemptFling = true;
// If we're moving down, we want the top item. If we're moving up, bottom item.
final int motionIndex = delta > 0 ? 0 : getChildCount() - 1;
@@ -4511,6 +4518,10 @@
final int overscrollMode = getOverScrollMode();
if (!trackMotionScroll(delta, delta)) {
+ if (shouldAttemptFling) {
+ initDifferentialFlingHelperIfNotExists();
+ mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
+ }
return true;
} else if (!event.isFromSource(InputDevice.SOURCE_MOUSE) && motionView != null
&& (overscrollMode == OVER_SCROLL_ALWAYS
@@ -4677,6 +4688,14 @@
}
}
+ private void initDifferentialFlingHelperIfNotExists() {
+ if (mDifferentialMotionFlingHelper == null) {
+ mDifferentialMotionFlingHelper =
+ new DifferentialMotionFlingHelper(
+ mContext, new DifferentialFlingTarget());
+ }
+ }
+
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -8197,4 +8216,26 @@
}
}
}
+
+ private class DifferentialFlingTarget
+ implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+ @Override
+ public boolean startDifferentialMotionFling(float velocity) {
+ stopDifferentialMotionFling();
+ fling((int) velocity);
+ return true;
+ }
+
+ @Override
+ public void stopDifferentialMotionFling() {
+ if (mFlingRunnable != null) {
+ mFlingRunnable.endFling();
+ }
+ }
+
+ @Override
+ public float getScaledScrollFactor() {
+ return -mVerticalScrollFactor;
+ }
+ }
}
diff --git a/core/java/android/widget/DifferentialMotionFlingHelper.java b/core/java/android/widget/DifferentialMotionFlingHelper.java
new file mode 100644
index 0000000..95d24ec
--- /dev/null
+++ b/core/java/android/widget/DifferentialMotionFlingHelper.java
@@ -0,0 +1,249 @@
+/*
+ * 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 android.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper for controlling differential motion flings.
+ *
+ * <p><b>Differential motion</b> here refers to motions that report change in position instead of
+ * absolution position. For instance, differential data points of 2, -1, 5 represent: there was
+ * a movement by "2" units, then by "-1" units, then by "5" units. Examples of motions reported
+ * differentially include motions from {@link MotionEvent#AXIS_SCROLL}.
+ *
+ * <p>The client should call {@link #onMotionEvent} when a differential motion event happens on
+ * the target View (that is, the View on which we want to fling), and this class processes the event
+ * to orchestrate fling.
+ *
+ * <p>Note that this helper class currently works to control fling only in one direction at a time.
+ * As such, it works independently of horizontal/vertical orientations. It requests its client to
+ * start/stop fling, and it's up to the client to choose the fling direction based on its specific
+ * internal configurations and/or preferences.
+ *
+ * @hide
+ */
+public class DifferentialMotionFlingHelper {
+ private final Context mContext;
+ private final DifferentialMotionFlingTarget mTarget;
+
+ private final FlingVelocityThresholdCalculator mVelocityThresholdCalculator;
+ private final DifferentialVelocityProvider mVelocityProvider;
+
+ @Nullable private VelocityTracker mVelocityTracker;
+
+ private float mLastFlingVelocity;
+
+ private int mLastProcessedAxis = -1;
+ private int mLastProcessedSource = -1;
+ private int mLastProcessedDeviceId = -1;
+
+ // Initialize min and max to +infinity and 0, to effectively disable fling at start.
+ private final int[] mFlingVelocityThresholds = new int[] {Integer.MAX_VALUE, 0};
+
+ /** Interface to calculate the fling velocity thresholds. Helps fake during testing. */
+ @VisibleForTesting
+ public interface FlingVelocityThresholdCalculator {
+ /**
+ * Calculates the fling velocity thresholds (in pixels/second) and puts them in a provided
+ * store.
+ *
+ * @param context the context associated with the View that may be flung.
+ * @param store an at-least size-2 int array. The method will overwrite positions 0 and 1
+ * with the min and max fling velocities, respectively.
+ * @param event the event that may trigger fling.
+ * @param axis the axis being processed for the event.
+ */
+ void calculateFlingVelocityThresholds(
+ Context context, int[] store, MotionEvent event, int axis);
+ }
+
+ /**
+ * Interface to provide velocity. Helps fake during testing.
+ *
+ * <p>The client should call {@link #getCurrentVelocity(VelocityTracker, MotionEvent, int)} each
+ * time it wants to consider a {@link MotionEvent} towards the latest velocity, and the
+ * interface handles providing velocity that accounts for the latest and all past events.
+ */
+ @VisibleForTesting
+ public interface DifferentialVelocityProvider {
+ /**
+ * Returns the latest velocity.
+ *
+ * @param vt the {@link VelocityTracker} to be used to compute velocity.
+ * @param event the latest event to be considered in the velocity computations.
+ * @param axis the axis being processed for the event.
+ * @return the calculated, latest velocity.
+ */
+ float getCurrentVelocity(VelocityTracker vt, MotionEvent event, int axis);
+ }
+
+ /**
+ * Represents an entity that may be flung by a differential motion or an entity that initiates
+ * fling on a target View.
+ */
+ public interface DifferentialMotionFlingTarget {
+ /**
+ * Start flinging on the target View by a given velocity.
+ *
+ * @param velocity the fling velocity, in pixels/second.
+ * @return {@code true} if fling was successfully initiated, {@code false} otherwise.
+ */
+ boolean startDifferentialMotionFling(float velocity);
+
+ /** Stop any ongoing fling on the target View that is caused by a differential motion. */
+ void stopDifferentialMotionFling();
+
+ /**
+ * Returns the scaled scroll factor to be used for differential motions. This is the
+ * value that the raw {@link MotionEvent} values should be multiplied with to get pixels.
+ *
+ * <p>This usually is one of the values provided by {@link ViewConfiguration}. It is
+ * up to the client to choose and provide any value as per its internal configuration.
+ *
+ * @see ViewConfiguration#getScaledHorizontalScrollFactor()
+ * @see ViewConfiguration#getScaledVerticalScrollFactor()
+ */
+ float getScaledScrollFactor();
+ }
+
+ /** Constructs an instance for a given {@link DifferentialMotionFlingTarget}. */
+ public DifferentialMotionFlingHelper(
+ Context context,
+ DifferentialMotionFlingTarget target) {
+ this(context,
+ target,
+ DifferentialMotionFlingHelper::calculateFlingVelocityThresholds,
+ DifferentialMotionFlingHelper::getCurrentVelocity);
+ }
+
+ @VisibleForTesting
+ public DifferentialMotionFlingHelper(
+ Context context,
+ DifferentialMotionFlingTarget target,
+ FlingVelocityThresholdCalculator velocityThresholdCalculator,
+ DifferentialVelocityProvider velocityProvider) {
+ mContext = context;
+ mTarget = target;
+ mVelocityThresholdCalculator = velocityThresholdCalculator;
+ mVelocityProvider = velocityProvider;
+ }
+
+ /**
+ * Called to report when a differential motion happens on the View that's the target for fling.
+ *
+ * @param event the {@link MotionEvent} being reported.
+ * @param axis the axis being processed by the target View.
+ */
+ public void onMotionEvent(MotionEvent event, int axis) {
+ boolean flingParamsChanged = calculateFlingVelocityThresholds(event, axis);
+ if (mFlingVelocityThresholds[0] == Integer.MAX_VALUE) {
+ // Integer.MAX_VALUE means that the device does not support fling for the current
+ // configuration. Do not proceed any further.
+ recycleVelocityTracker();
+ return;
+ }
+
+ float scaledVelocity =
+ getCurrentVelocity(event, axis) * mTarget.getScaledScrollFactor();
+
+ float velocityDirection = Math.signum(scaledVelocity);
+ // Stop ongoing fling if there has been state changes affecting fling, or if the current
+ // velocity (if non-zero) is opposite of the velocity that last caused fling.
+ if (flingParamsChanged
+ || (velocityDirection != Math.signum(mLastFlingVelocity)
+ && velocityDirection != 0)) {
+ mTarget.stopDifferentialMotionFling();
+ }
+
+ if (Math.abs(scaledVelocity) < mFlingVelocityThresholds[0]) {
+ return;
+ }
+
+ // Clamp the scaled velocity between [-max, max].
+ // e.g. if max=100, and vel=200
+ // vel = max(-100, min(200, 100)) = max(-100, 100) = 100
+ // e.g. if max=100, and vel=-200
+ // vel = max(-100, min(-200, 100)) = max(-100, -200) = -100
+ scaledVelocity =
+ Math.max(
+ -mFlingVelocityThresholds[1],
+ Math.min(scaledVelocity, mFlingVelocityThresholds[1]));
+
+ boolean flung = mTarget.startDifferentialMotionFling(scaledVelocity);
+ mLastFlingVelocity = flung ? scaledVelocity : 0;
+ }
+
+ /**
+ * Calculates fling velocity thresholds based on the provided event and axis, and returns {@code
+ * true} if there has been a change of any params that may affect fling velocity thresholds.
+ */
+ private boolean calculateFlingVelocityThresholds(MotionEvent event, int axis) {
+ int source = event.getSource();
+ int deviceId = event.getDeviceId();
+ if (mLastProcessedSource != source
+ || mLastProcessedDeviceId != deviceId
+ || mLastProcessedAxis != axis) {
+ mVelocityThresholdCalculator.calculateFlingVelocityThresholds(
+ mContext, mFlingVelocityThresholds, event, axis);
+ // Save data about this processing so that we don't have to re-process fling thresholds
+ // for similar parameters.
+ mLastProcessedSource = source;
+ mLastProcessedDeviceId = deviceId;
+ mLastProcessedAxis = axis;
+ return true;
+ }
+ return false;
+ }
+
+ private static void calculateFlingVelocityThresholds(
+ Context context, int[] buffer, MotionEvent event, int axis) {
+ int source = event.getSource();
+ int deviceId = event.getDeviceId();
+
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ buffer[0] = vc.getScaledMinimumFlingVelocity(deviceId, axis, source);
+ buffer[1] = vc.getScaledMaximumFlingVelocity(deviceId, axis, source);
+ }
+
+ private float getCurrentVelocity(MotionEvent event, int axis) {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+
+ return mVelocityProvider.getCurrentVelocity(mVelocityTracker, event, axis);
+ }
+
+ private void recycleVelocityTracker() {
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private static float getCurrentVelocity(VelocityTracker vt, MotionEvent event, int axis) {
+ vt.addMovement(event);
+ vt.computeCurrentVelocity(1000);
+ return vt.getAxisVelocity(axis);
+ }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 58f6613..f99f138 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8207,7 +8207,8 @@
*/
void beforeSetText() {
// TextView#setText is called because our call to
- // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode().
+ // TextView#setTransformationMethodInternal in enterInsertMode(), exitInsertMode() or
+ // updateTransformationMethod().
// Do nothing in this case.
if (mUpdatingTransformationMethod) {
return;
@@ -8218,22 +8219,28 @@
}
/**
- * Notify the {@link InsertModeController} before the TextView's
- * {@link TransformationMethod} is updated. If it's not in the insert mode,
- * the given method is directly returned. Otherwise, it will wrap the given transformation
- * method with an {@link InsertModeTransformationMethod} and then return.
+ * Notify the {@link InsertModeController} that TextView#setTransformationMethod is called.
+ * If it's not in the insert mode, the given transformation method is directly set to the
+ * TextView. Otherwise, it will wrap the given transformation method with an
+ * {@link InsertModeTransformationMethod} and then set it on the TextView.
*
- * @param oldTransformationMethod the new {@link TransformationMethod} to be set on the
+ * @param transformationMethod the new {@link TransformationMethod} to be set on the
* TextView.
- * @return the updated {@link TransformationMethod} to be set on the Textview.
*/
- TransformationMethod updateTransformationMethod(
- TransformationMethod oldTransformationMethod) {
- if (!mIsInsertModeActive) return oldTransformationMethod;
+ void updateTransformationMethod(TransformationMethod transformationMethod) {
+ if (!mIsInsertModeActive) {
+ setTransformationMethod(transformationMethod, /* updateText */ true);
+ return;
+ }
+ // Changing TransformationMethod will reset selection range to [0, 0), we need to
+ // manually restore the old selection range.
+ final int selectionStart = mTextView.getSelectionStart();
+ final int selectionEnd = mTextView.getSelectionEnd();
mInsertModeTransformationMethod = mInsertModeTransformationMethod.update(
- oldTransformationMethod, mTextView.isSingleLine());
- return mInsertModeTransformationMethod;
+ transformationMethod, mTextView.isSingleLine());
+ setTransformationMethod(mInsertModeTransformationMethod, /* updateText */ true);
+ Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
}
}
@@ -8259,18 +8266,11 @@
* @param method the {@link TransformationMethod} to be set on the TextView.
*/
void setTransformationMethod(TransformationMethod method) {
- if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
+ if (mInsertModeController == null) {
mTextView.setTransformationMethodInternal(method, /* updateText */ true);
return;
}
-
- // Changing TransformationMethod will reset selection range to [0, 0), we need to
- // manually restore the old selection range.
- final int selectionStart = mTextView.getSelectionStart();
- final int selectionEnd = mTextView.getSelectionEnd();
- method = mInsertModeController.updateTransformationMethod(method);
- mTextView.setTransformationMethodInternal(method, /* updateText */ true);
- Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+ mInsertModeController.updateTransformationMethod(method);
}
/**
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index cb5dbe6..eeb6b43 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -204,6 +204,8 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private StrictMode.Span mFlingStrictSpan = null;
+ private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
@@ -594,6 +596,14 @@
}
}
+ private void initDifferentialFlingHelperIfNotExists() {
+ if (mDifferentialMotionFlingHelper == null) {
+ mDifferentialMotionFlingHelper =
+ new DifferentialMotionFlingHelper(
+ mContext, new DifferentialFlingTarget());
+ }
+ }
+
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -942,17 +952,22 @@
public boolean onGenericMotionEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
- final float axisValue;
+ final int axis;
if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
- axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+ axis = MotionEvent.AXIS_VSCROLL;
} else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
- axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
+ axis = MotionEvent.AXIS_SCROLL;
} else {
- axisValue = 0;
+ axis = -1;
}
+ final float axisValue = (axis == -1) ? 0 : event.getAxisValue(axis);
final int delta = Math.round(axisValue * mVerticalScrollFactor);
if (delta != 0) {
+ // Tracks whether or not we should attempt fling for this event.
+ // Fling should not be attempted if the view is already at the limit of scroll,
+ // since it conflicts with EdgeEffect.
+ boolean shouldAttemptFling = true;
final int range = getScrollRange();
int oldScrollY = mScrollY;
int newScrollY = oldScrollY - delta;
@@ -971,6 +986,7 @@
absorbed = true;
}
newScrollY = 0;
+ shouldAttemptFling = false;
} else if (newScrollY > range) {
if (canOverscroll) {
mEdgeGlowBottom.onPullDistance(
@@ -980,9 +996,14 @@
absorbed = true;
}
newScrollY = range;
+ shouldAttemptFling = false;
}
if (newScrollY != oldScrollY) {
super.scrollTo(mScrollX, newScrollY);
+ if (shouldAttemptFling) {
+ initDifferentialFlingHelperIfNotExists();
+ mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
+ }
return true;
}
if (absorbed) {
@@ -2126,4 +2147,23 @@
};
}
+ private class DifferentialFlingTarget
+ implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+ @Override
+ public boolean startDifferentialMotionFling(float velocity) {
+ stopDifferentialMotionFling();
+ fling((int) velocity);
+ return true;
+ }
+
+ @Override
+ public void stopDifferentialMotionFling() {
+ mScroller.abortAnimation();
+ }
+
+ @Override
+ public float getScaledScrollFactor() {
+ return -mVerticalScrollFactor;
+ }
+ }
}
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 99e63ec..c9ac245 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -86,6 +86,7 @@
* @param token The token used to attach to a window manager node. It is usually from
* {@link Context#getWindowContextToken()}.
*/
+ @VisibleForTesting
public WindowContextController(@NonNull WindowTokenClient token) {
mToken = token;
}
@@ -104,7 +105,7 @@
throw new IllegalStateException("A Window Context can be only attached to "
+ "a DisplayArea once.");
}
- mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea(
+ mAttachedToDisplayArea = getWindowTokenClientController().attachToDisplayArea(
mToken, type, displayId, options)
? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
@@ -136,22 +137,31 @@
* @see WindowProviderService#attachToWindowToken(IBinder))
* @see IWindowManager#attachWindowContextToWindowToken
*/
- public void attachToWindowToken(IBinder windowToken) {
+ public void attachToWindowToken(@NonNull IBinder windowToken) {
if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) {
throw new IllegalStateException("The Window Context should have been attached"
+ " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
}
- WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken);
+ if (!getWindowTokenClientController().attachToWindowToken(mToken, windowToken)) {
+ Log.e(TAG, "attachToWindowToken fail");
+ }
}
/** Detaches the window context from the node it's currently associated with. */
public void detachIfNeeded() {
if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
- WindowTokenClientController.getInstance().detachIfNeeded(mToken);
+ getWindowTokenClientController().detachIfNeeded(mToken);
mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
if (DEBUG_ATTACH) {
Log.d(TAG, "Detach Window Context.");
}
}
}
+
+ /** Gets the {@link WindowTokenClientController}. */
+ @VisibleForTesting
+ @NonNull
+ public WindowTokenClientController getWindowTokenClientController() {
+ return WindowTokenClientController.getInstance();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/core/java/android/window/WindowContextInfo.aidl
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
rename to core/java/android/window/WindowContextInfo.aidl
index 64f5087..360431c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
+++ b/core/java/android/window/WindowContextInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.model
+package android.window;
-object SceneContainerNames {
- const val SYSTEM_UI_DEFAULT = "system_ui"
-}
+/** @hide */
+parcelable WindowContextInfo;
\ No newline at end of file
diff --git a/core/java/android/window/WindowContextInfo.java b/core/java/android/window/WindowContextInfo.java
new file mode 100644
index 0000000..3c21cd4
--- /dev/null
+++ b/core/java/android/window/WindowContextInfo.java
@@ -0,0 +1,117 @@
+/*
+ * 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.window;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Stores information about a particular window that a {@link WindowContext} is attached to.
+ * @hide
+ */
+public class WindowContextInfo implements Parcelable {
+
+ /**
+ * Configuration of the window.
+ */
+ @NonNull
+ private final Configuration mConfiguration;
+
+ /**
+ * The display id that the window is attached to.
+ */
+ private final int mDisplayId;
+
+ public WindowContextInfo(@NonNull Configuration configuration, int displayId) {
+ mConfiguration = requireNonNull(configuration);
+ mDisplayId = displayId;
+ }
+
+ @NonNull
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ // Parcelable implementation
+
+ /** Writes to Parcel. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ dest.writeInt(mDisplayId);
+ }
+
+ /** Reads from Parcel. */
+ private WindowContextInfo(@NonNull Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mDisplayId = in.readInt();
+ }
+
+ public static final @NonNull Creator<WindowContextInfo> CREATOR = new Creator<>() {
+ public WindowContextInfo createFromParcel(Parcel in) {
+ return new WindowContextInfo(in);
+ }
+
+ public WindowContextInfo[] newArray(int size) {
+ return new WindowContextInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowContextInfo other = (WindowContextInfo) o;
+ return Objects.equals(mConfiguration, other.mConfiguration)
+ && mDisplayId == other.mDisplayId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mConfiguration);
+ result = 31 * result + mDisplayId;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "WindowContextInfo{config=" + mConfiguration
+ + ", displayId=" + mDisplayId
+ + "}";
+ }
+}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 7458563..6a32529 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -99,6 +99,13 @@
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
// TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction
+ postOnConfigurationChanged(newConfig, newDisplayId);
+ }
+
+ /**
+ * Posts an {@link #onConfigurationChanged} to the main thread.
+ */
+ public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
}
@@ -162,7 +169,6 @@
windowContext.dispatchConfigurationChanged(newConfig);
}
-
if (shouldReportConfigChange && diff != 0
&& context instanceof WindowProviderService) {
final WindowProviderService windowProviderService = (WindowProviderService) context;
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index 4484707..7a84123 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -17,22 +17,21 @@
package android.window;
import static android.view.WindowManager.LayoutParams.WindowType;
-import static android.view.WindowManagerGlobal.getWindowManagerService;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.app.IApplicationThread;
-import android.app.servertransaction.WindowContextConfigurationChangeItem;
+import android.app.servertransaction.WindowContextInfoChangeItem;
import android.app.servertransaction.WindowContextWindowRemovalItem;
import android.content.Context;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -56,6 +55,7 @@
private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
/** Gets the singleton controller. */
+ @NonNull
public static WindowTokenClientController getInstance() {
synchronized (WindowTokenClientController.class) {
if (sController == null) {
@@ -75,6 +75,7 @@
/** Creates a new instance for test only. */
@VisibleForTesting
+ @NonNull
public static WindowTokenClientController createInstanceForTesting() {
return new WindowTokenClientController();
}
@@ -92,17 +93,17 @@
*/
public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
@WindowType int type, int displayId, @Nullable Bundle options) {
- final Configuration configuration;
+ final WindowContextInfo info;
try {
- configuration = getWindowManagerService().attachWindowContextToDisplayArea(
+ info = getWindowManagerService().attachWindowContextToDisplayArea(
mAppThread, client, type, displayId, options);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- if (configuration == null) {
+ if (info == null) {
return false;
}
- onWindowContextTokenAttached(client, displayId, configuration);
+ onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
return true;
}
@@ -119,16 +120,16 @@
if (wms == null) {
return false;
}
- final Configuration configuration;
+ final WindowContextInfo info;
try {
- configuration = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
+ info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- if (configuration == null) {
+ if (info == null) {
return false;
}
- onWindowContextTokenAttached(client, displayId, configuration);
+ onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
return true;
}
@@ -137,19 +138,23 @@
*
* @param client The {@link WindowTokenClient} to attach.
* @param windowToken the window token to associated with
+ * @return {@code true} if attaching successfully.
*/
- public void attachToWindowToken(@NonNull WindowTokenClient client,
+ public boolean attachToWindowToken(@NonNull WindowTokenClient client,
@NonNull IBinder windowToken) {
+ final WindowContextInfo info;
try {
- getWindowManagerService().attachWindowContextToWindowToken(
+ info = getWindowManagerService().attachWindowContextToWindowToken(
mAppThread, client, windowToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- // We don't report configuration change for now.
- synchronized (mLock) {
- mWindowTokenClientMap.put(client.asBinder(), client);
+ if (info == null) {
+ return false;
}
+ // We currently report configuration for WindowToken after attached.
+ onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */);
+ return true;
}
/** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
@@ -166,21 +171,30 @@
}
}
- private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, int displayId,
- @NonNull Configuration configuration) {
+ private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
+ @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
synchronized (mLock) {
mWindowTokenClientMap.put(client.asBinder(), client);
}
- client.onConfigurationChanged(configuration, displayId,
- false /* shouldReportConfigChange */);
+ if (shouldReportConfigChange) {
+ // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the
+ // dispatch in the next loop to prevent the callback from being dispatched before
+ // #onCreate or WindowContext creation..
+ client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId());
+ } else {
+ // Apply the config change directly in case users get stale values after WindowContext
+ // creation.
+ client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(),
+ false /* shouldReportConfigChange */);
+ }
}
- /** Called when receives {@link WindowContextConfigurationChangeItem}. */
- public void onWindowContextConfigurationChanged(@NonNull IBinder clientToken,
- @NonNull Configuration configuration, int displayId) {
+ /** Called when receives {@link WindowContextInfoChangeItem}. */
+ public void onWindowContextInfoChanged(@NonNull IBinder clientToken,
+ @NonNull WindowContextInfo info) {
final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
if (windowTokenClient != null) {
- windowTokenClient.onConfigurationChanged(configuration, displayId);
+ windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
}
}
@@ -203,4 +217,11 @@
}
return windowTokenClient;
}
+
+ /** Gets the {@link IWindowManager}. */
+ @VisibleForTesting
+ @Nullable
+ public IWindowManager getWindowManagerService() {
+ return WindowManagerGlobal.getWindowManagerService();
+ }
}
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index 3de107e..ddfd0ed 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -1,14 +1,22 @@
{
"presubmit": [
{
- "name": "CtsRoleTestCases"
+ "name": "CtsRoleTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
},
{
"name": "CtsPermissionTestCases",
"options": [
- {
- "include-filter": "android.permission.cts.PermissionControllerTest"
- }
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
]
},
{
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 8fc30d1..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
appPackageNameChars.c_str(), vulkanVersion);
}
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
jstring packageName, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
}
}
- android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+ android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
packageNameChars.c_str(), features);
}
@@ -118,7 +118,7 @@
reinterpret_cast<void*>(setGpuStats_native)},
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
- {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+ {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index d8a2497..ffe844d 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -41,7 +41,7 @@
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
typedef void (*APH_closeSession)(APerformanceHintSession* session);
typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
-typedef void (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
+typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
bool gAPerformanceHintBindingInitialized = false;
@@ -112,6 +112,20 @@
} // namespace
+static void throwExceptionForErrno(JNIEnv* env, int err, const std::string& msg) {
+ switch (err) {
+ case EINVAL:
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+ break;
+ case EPERM:
+ jniThrowExceptionFmt(env, "java/lang/SecurityException", msg.c_str());
+ break;
+ default:
+ jniThrowException(env, "java/lang/RuntimeException", msg.c_str());
+ break;
+ }
+}
+
static jlong nativeAcquireManager(JNIEnv* env, jclass clazz) {
ensureAPerformanceHintBindingInitialized();
return reinterpret_cast<jlong>(gAPH_getManagerFn());
@@ -174,8 +188,11 @@
for (size_t i = 0; i < tidsArray.size(); ++i) {
tidsVector.push_back(static_cast<int32_t>(tidsArray[i]));
}
- gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
- tidsVector.data(), tidsVector.size());
+ int err = gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+ tidsVector.data(), tidsVector.size());
+ if (err != 0) {
+ throwExceptionForErrno(env, err, "Failed to set threads for hint session");
+ }
}
// This call should only be used for validation in tests only. This call will initiate two IPC
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 325ebbe..8e619a8 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -609,4 +609,6 @@
optional bool animation_in_progress = 1;
optional int32 last_back_type = 2;
optional bool show_wallpaper = 3;
+ optional string main_open_activity = 4;
+ optional bool animation_running = 5;
}
\ No newline at end of file
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index baa47da..b29a4e6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5631,6 +5631,12 @@
android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
+ <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ Allows an application to send and receive messages via CDM transports.
+ -->
+ <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
+ android:protectionLevel="signature|module" />
+
<!-- Allows an application to create new companion device associations.
@SystemApi
@hide -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 54dff08..7300772 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3938,8 +3938,14 @@
<!-- Flag indicating device support for EAP SIM, AKA, AKA' -->
<bool name="config_eap_sim_based_auth_supported">true</bool>
+ <!-- How long history of recent vibrations should be kept for the dumpsys. -->
+ <integer name="config_recentVibrationsDumpSizeLimit">20</integer>
+
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
- <integer name="config_previousVibrationsDumpLimit">50</integer>
+ <integer name="config_previousVibrationsDumpSizeLimit">50</integer>
+
+ <!-- How close vibration request should be when they're aggregated for dumpsys, in ms. -->
+ <integer name="config_previousVibrationsDumpAggregationTimeMillisLimit">1000</integer>
<!-- The default vibration strength, must be between 1 and 255 inclusive. -->
<integer name="config_defaultVibrationAmplitude">255</integer>
@@ -5559,10 +5565,6 @@
<!-- pdp data reject retry delay in ms -->
<integer name="config_pdp_reject_retry_delay_ms">-1</integer>
- <!-- Duration in milliseconds for device to vibrate on mash press on power
- button. -->
- <integer name="config_mashPressVibrateTimeOnPowerButton">0</integer>
-
<!-- Whether or not to enable the binder heavy hitter watcher by default -->
<bool name="config_defaultBinderHeavyHitterWatcherEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9da0f17..391d7bd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2038,7 +2038,9 @@
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
- <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" />
+ <java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" />
+ <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
<java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
@@ -4947,8 +4949,6 @@
<java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
<java-symbol type="array" name="config_gnssParameters" />
- <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" />
-
<java-symbol type="string" name="config_systemGameService" />
<java-symbol type="string" name="config_supervisedUserCreationPackage"/>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 8da6d74..c904d96 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -64,6 +64,7 @@
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.View;
+import android.window.WindowContextInfo;
import android.window.WindowTokenClientController;
import androidx.test.filters.MediumTest;
@@ -753,13 +754,12 @@
WindowTokenClientController.overrideForTesting(windowTokenClientController);
final IBinder clientToken = mock(IBinder.class);
final Configuration configuration = new Configuration();
+ final WindowContextInfo info = new WindowContextInfo(configuration, DEFAULT_DISPLAY);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
- .handleWindowContextConfigurationChanged(
- clientToken, configuration, DEFAULT_DISPLAY));
+ .handleWindowContextInfoChanged(clientToken, info));
- verify(windowTokenClientController).onWindowContextConfigurationChanged(
- clientToken, configuration, DEFAULT_DISPLAY);
+ verify(windowTokenClientController).onWindowContextInfoChanged(clientToken, info);
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
similarity index 79%
rename from core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
rename to core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
index 7811e1a..37a517e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
@@ -23,6 +23,7 @@
import android.app.ClientTransactionHandler;
import android.content.res.Configuration;
import android.os.IBinder;
+import android.window.WindowContextInfo;
import org.junit.Before;
import org.junit.Test;
@@ -30,12 +31,12 @@
import org.mockito.MockitoAnnotations;
/**
- * Tests for {@link WindowContextConfigurationChangeItem}.
+ * Tests for {@link WindowContextInfoChangeItem}.
*
* Build/Install/Run:
- * atest FrameworksCoreTests:WindowContextConfigurationChangeItemTest
+ * atest FrameworksCoreTests:WindowContextInfoChangeItemTest
*/
-public class WindowContextConfigurationChangeItemTest {
+public class WindowContextInfoChangeItemTest {
@Mock
private ClientTransactionHandler mHandler;
@@ -55,11 +56,11 @@
@Test
public void testExecute() {
- final WindowContextConfigurationChangeItem item = WindowContextConfigurationChangeItem
+ final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
.obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
item.execute(mHandler, mToken, mPendingActions);
- verify(mHandler).handleWindowContextConfigurationChanged(mClientToken, mConfiguration,
- DEFAULT_DISPLAY);
+ verify(mHandler).handleWindowContextInfoChanged(mClientToken,
+ new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
}
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 7eefbbc..2c03fdc 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -139,11 +139,20 @@
}
@Test
- public void testSetThreadsWithIllegalArgument() {
+ public void testSetThreads_emptyTids() {
Session session = createSession();
assumeNotNull(session);
assertThrows(IllegalArgumentException.class, () -> {
- session.setThreads(new int[] { });
+ session.setThreads(new int[]{});
+ });
+ }
+
+ @Test
+ public void testSetThreads_invalidTids() {
+ Session session = createSession();
+ assumeNotNull(session);
+ assertThrows(SecurityException.class, () -> {
+ session.setThreads(new int[]{-1});
});
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index fde1a6d..b06cd39 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -43,6 +43,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -290,6 +291,18 @@
}
@Test
+ public void testCalculateInsets_emptyIme() {
+ WindowInsets insets1 = mState.calculateInsets(new Rect(), null, false, false,
+ SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ mState.getOrCreateSource(ID_IME, ime());
+ WindowInsets insets2 = mState.calculateInsets(new Rect(), null, false, false,
+ SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
+ assertEquals(Insets.NONE, insets1.getInsets(ime()));
+ assertEquals(Insets.NONE, insets2.getInsets(ime()));
+ assertEquals(insets1, insets2);
+ }
+
+ @Test
public void testStripForDispatch() {
mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
.setFrame(new Rect(0, 0, 100, 100))
@@ -304,6 +317,73 @@
}
@Test
+ public void testEquals() {
+ final InsetsState state1 = new InsetsState();
+ final InsetsState state2 = new InsetsState();
+ assertTrue(state1.equals(state2));
+
+ state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+ assertFalse(state1.equals(state2));
+
+ state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+ assertTrue(state1.equals(state2));
+
+ state2.addSource(new InsetsSource(ID_NAVIGATION_BAR, navigationBars()));
+ assertFalse(state1.equals(state2));
+ }
+
+ @Test
+ public void testEquals_excludesCaptionBar() {
+ final InsetsState state1 = new InsetsState();
+ final InsetsState state2 = new InsetsState();
+
+ state1.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 5));
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertTrue(state1.equals(
+ state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+
+ state2.addSource(new InsetsSource(ID_CAPTION_BAR, captionBar()).setFrame(0, 0, 0, 10));
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertTrue(state1.equals(
+ state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+
+ state1.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+ state2.addSource(new InsetsSource(ID_STATUS_BAR, statusBars()));
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertTrue(state1.equals(
+ state2, true /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ }
+
+ @Test
+ public void testEquals_excludesInvisibleIme() {
+ final InsetsState state1 = new InsetsState();
+ final InsetsState state2 = new InsetsState();
+
+ final InsetsSource imeSource1 = new InsetsSource(ID_IME, ime()).setVisible(true);
+ state1.addSource(imeSource1);
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+
+ imeSource1.setVisible(false);
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertTrue(state1.equals(
+ state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+
+ final InsetsSource imeSource2 = new InsetsSource(ID_IME, ime()).setFrame(0, 0, 0, 10);
+ state2.addSource(imeSource2);
+ assertFalse(state1.equals(
+ state2, false /* excludesCaptionBar */, false /* excludesInvisibleIme */));
+ assertTrue(state1.equals(
+ state2, false /* excludesCaptionBar */, true /* excludesInvisibleIme */));
+ }
+
+ @Test
public void testEquals_differentRect() {
mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
.setFrame(new Rect(0, 0, 100, 100));
@@ -404,17 +484,6 @@
}
@Test
- public void testEquals_excludeInvisibleIme() {
- mState.getOrCreateSource(ID_IME, ime())
- .setFrame(new Rect(0, 0, 100, 100))
- .setVisible(false);
- mState2.getOrCreateSource(ID_IME, ime())
- .setFrame(new Rect(0, 0, 100, 200))
- .setVisible(false);
- assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */));
- }
-
- @Test
public void testParcelUnparcel() {
mState.getOrCreateSource(ID_IME, ime())
.setFrame(new Rect(0, 0, 100, 100))
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index b37c8fd..bce3f3e 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -49,11 +49,11 @@
public void testDispatchMouseEventsUnderCursor() {
final Context context = getInstrumentation().getContext();
final TestView viewGroup = new TestView(context, 0 /* left */, 0 /* top */,
- 200 /* right */, 200 /* bottom */);
+ 200 /* right */, 100 /* bottom */);
final TestView viewA = spy(new TestView(context, 0 /* left */, 0 /* top */,
- 100 /* right */, 200 /* bottom */));
+ 100 /* right */, 100 /* bottom */));
final TestView viewB = spy(new TestView(context, 100 /* left */, 0 /* top */,
- 200 /* right */, 200 /* bottom */));
+ 200 /* right */, 100 /* bottom */));
viewGroup.addView(viewA);
viewGroup.addView(viewB);
@@ -73,10 +73,10 @@
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2];
coords[0] = new MotionEvent.PointerCoords();
coords[0].x = 80;
- coords[0].y = 100;
+ coords[0].y = 50;
coords[1] = new MotionEvent.PointerCoords();
coords[1].x = 240;
- coords[1].y = 100;
+ coords[1].y = 50;
MotionEvent event;
// Make sure the down event is active with a pointer which coordinate is different from the
@@ -91,6 +91,10 @@
viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */);
verify(viewB).onResolvePointerIcon(event, 0);
+ event.setAction(MotionEvent.ACTION_SCROLL);
+ viewGroup.dispatchGenericMotionEvent(event);
+ verify(viewB).dispatchGenericMotionEvent(event);
+
event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
@@ -102,8 +106,13 @@
viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */);
verify(viewB).onResolvePointerIcon(event, 1);
+ event.setAction(MotionEvent.ACTION_SCROLL);
+ viewGroup.dispatchGenericMotionEvent(event);
+ verify(viewB).dispatchGenericMotionEvent(event);
+
verify(viewA, never()).dispatchTouchEvent(any());
verify(viewA, never()).onResolvePointerIcon(any(), anyInt());
+ verify(viewA, never()).dispatchGenericMotionEvent(any());
}
/**
diff --git a/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
new file mode 100644
index 0000000..51c8bc0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.view.MotionEvent;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DifferentialMotionFlingHelperTest {
+ private int mMinVelocity = 0;
+ private int mMaxVelocity = Integer.MAX_VALUE;
+ /** A fake velocity value that's going to be returned from the velocity provider. */
+ private float mVelocity;
+ private boolean mVelocityCalculated;
+
+ private final DifferentialMotionFlingHelper.DifferentialVelocityProvider mVelocityProvider =
+ (vt, event, axis) -> {
+ mVelocityCalculated = true;
+ return mVelocity;
+ };
+
+ private final DifferentialMotionFlingHelper.FlingVelocityThresholdCalculator
+ mVelocityThresholdCalculator =
+ (ctx, buffer, event, axis) -> {
+ buffer[0] = mMinVelocity;
+ buffer[1] = mMaxVelocity;
+ };
+
+ private final TestDifferentialMotionFlingTarget mFlingTarget =
+ new TestDifferentialMotionFlingTarget();
+
+ private DifferentialMotionFlingHelper mFlingHelper;
+
+ @Before
+ public void setUp() throws Exception {
+ mFlingHelper = new DifferentialMotionFlingHelper(
+ ApplicationProvider.getApplicationContext(),
+ mFlingTarget,
+ mVelocityThresholdCalculator,
+ mVelocityProvider);
+ }
+
+ @Test
+ public void deviceDoesNotSupportFling_noVelocityCalculated() {
+ mMinVelocity = Integer.MAX_VALUE;
+ mMaxVelocity = Integer.MIN_VALUE;
+
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
+
+ assertFalse(mVelocityCalculated);
+ }
+
+ @Test
+ public void flingVelocityOppositeToPrevious_stopsOngoingFling() {
+ deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 50);
+ deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, -10);
+
+ // One stop on the initial event, and second stop due to opposite velocities.
+ assertEquals(2, mFlingTarget.mNumStops);
+ }
+
+ @Test
+ public void flingParamsChanged_stopsOngoingFling() {
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 50);
+ deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 10);
+
+ // One stop on the initial event, and second stop due to changed axis/source.
+ assertEquals(2, mFlingTarget.mNumStops);
+ }
+
+ @Test
+ public void positiveFlingVelocityTooLow_doesNotGenerateFling() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 20);
+
+ assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ @Test
+ public void negativeFlingVelocityTooLow_doesNotGenerateFling() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -20);
+
+ assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ @Test
+ public void positiveFlingVelocityAboveMinimum_generateFlings() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
+
+ assertEquals(60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ @Test
+ public void negativeFlingVelocityAboveMinimum_generateFlings() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -60);
+
+ assertEquals(-60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ @Test
+ public void positiveFlingVelocityAboveMaximum_velocityClamped() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 3000);
+
+ assertEquals(100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ @Test
+ public void negativeFlingVelocityAboveMaximum_velocityClamped() {
+ mMinVelocity = 50;
+ mMaxVelocity = 100;
+ deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -3000);
+
+ assertEquals(-100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+ }
+
+ private MotionEvent createRotaryEncoderEvent() {
+ return MotionEventUtils.createRotaryEvent(-2);
+ }
+
+ private MotionEvent createPointerEvent() {
+ return MotionEventUtils.createGenericPointerEvent(/* hScroll= */ 0, /* vScroll= */ -1);
+
+ }
+
+ private void deliverEventWithVelocity(MotionEvent ev, int axis, float velocity) {
+ mVelocity = velocity;
+ mFlingHelper.onMotionEvent(ev, axis);
+ ev.recycle();
+ }
+
+ private static class TestDifferentialMotionFlingTarget
+ implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+ float mLastFlingVelocity = 0;
+ int mNumStops = 0;
+
+ @Override
+ public boolean startDifferentialMotionFling(float velocity) {
+ mLastFlingVelocity = velocity;
+ return true;
+ }
+
+ @Override
+ public void stopDifferentialMotionFling() {
+ mNumStops++;
+ }
+
+ @Override
+ public float getScaledScrollFactor() {
+ return 1;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/MotionEventUtils.java b/core/tests/coretests/src/android/widget/MotionEventUtils.java
new file mode 100644
index 0000000..275efa3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/MotionEventUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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 android.widget;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
+import static android.view.MotionEvent.ACTION_SCROLL;
+import static android.view.MotionEvent.AXIS_HSCROLL;
+import static android.view.MotionEvent.AXIS_SCROLL;
+import static android.view.MotionEvent.AXIS_VSCROLL;
+
+import android.view.MotionEvent;
+
+/** Test utilities for {@link MotionEvent}s. */
+public class MotionEventUtils {
+
+ /** Creates a test {@link MotionEvent} from a {@link SOURCE_ROTARY_ENCODER}. */
+ public static MotionEvent createRotaryEvent(float scroll) {
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.setAxisValue(AXIS_SCROLL, scroll);
+
+ return createGenericMotionEvent(SOURCE_ROTARY_ENCODER, ACTION_SCROLL, coords);
+ }
+
+ /** Creates a test {@link MotionEvent} from a {@link SOURCE_CLASS_POINTER}. */
+ public static MotionEvent createGenericPointerEvent(float hScroll, float vScroll) {
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.setAxisValue(AXIS_HSCROLL, hScroll);
+ coords.setAxisValue(AXIS_VSCROLL, vScroll);
+
+ return createGenericMotionEvent(SOURCE_CLASS_POINTER, ACTION_SCROLL, coords);
+ }
+
+ private static MotionEvent createGenericMotionEvent(
+ int source, int action, MotionEvent.PointerCoords coords) {
+ MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
+ props.id = 0;
+
+ return MotionEvent.obtain(
+ /* downTime= */ 0,
+ /* eventTime= */ 100,
+ action,
+ /* pointerCount= */ 1,
+ new MotionEvent.PointerProperties[] {props},
+ new MotionEvent.PointerCoords[] {coords},
+ /* metaState= */ 0,
+ /* buttonState= */ 0,
+ /* xPrecision= */ 0,
+ /* yPrecision= */ 0,
+ /* deviceId= */ 1,
+ /* edgeFlags= */ 0,
+ source,
+ /* flags= */ 0);
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 940c067..30c0f2b 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.os.Binder;
@@ -36,7 +37,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -62,24 +62,16 @@
@Mock
private WindowTokenClient mMockToken;
- private WindowTokenClientController mOriginalController;
-
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mController = new WindowContextController(mMockToken);
+ mController = spy(new WindowContextController(mMockToken));
+ doReturn(mWindowTokenClientController).when(mController).getWindowTokenClientController();
doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
- mOriginalController = WindowTokenClientController.getInstance();
- WindowTokenClientController.overrideForTesting(mWindowTokenClientController);
doReturn(true).when(mWindowTokenClientController).attachToDisplayArea(
eq(mMockToken), anyInt(), anyInt(), any());
}
- @After
- public void tearDown() {
- WindowTokenClientController.overrideForTesting(mOriginalController);
- }
-
@Test(expected = IllegalStateException.class)
public void testAttachToDisplayAreaTwiceThrowException() {
mController.attachToDisplayArea(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 767dd8c..7bd6f05 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -35,11 +35,9 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import androidx.test.filters.SmallTest;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -66,22 +64,16 @@
// Can't mock final class.
private final Configuration mConfiguration = new Configuration();
- private IWindowManager mOriginalWindowManagerService;
-
+ private WindowContextInfo mWindowContextInfo;
private WindowTokenClientController mController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService);
doReturn(mClientToken).when(mWindowTokenClient).asBinder();
mController = spy(WindowTokenClientController.createInstanceForTesting());
- }
-
- @After
- public void tearDown() {
- WindowManagerGlobal.overrideWindowManagerServiceForTesting(mOriginalWindowManagerService);
+ doReturn(mWindowManagerService).when(mController).getWindowManagerService();
+ mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY);
}
@Test
@@ -96,7 +88,7 @@
TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */);
verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
- doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+ doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToDisplayArea(
any(), any(), anyInt(), anyInt(), any());
assertTrue(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
@@ -119,13 +111,13 @@
verify(mWindowManagerService, never()).detachWindowContext(any());
- doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+ doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToDisplayArea(
any(), any(), anyInt(), anyInt(), any());
mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
DEFAULT_DISPLAY, null /* options */);
mController.detachIfNeeded(mWindowTokenClient);
- verify(mWindowManagerService).detachWindowContext(any());
+ verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
}
@Test
@@ -139,8 +131,8 @@
DEFAULT_DISPLAY);
verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
- doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
- any(), any(), anyInt());
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToDisplayContent(any(), any(), anyInt());
assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY,
@@ -160,21 +152,30 @@
verify(mWindowManagerService, never()).detachWindowContext(any());
- doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
- any(), any(), anyInt());
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToDisplayContent(any(), any(), anyInt());
mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY);
mController.detachIfNeeded(mWindowTokenClient);
- verify(mWindowManagerService).detachWindowContext(any());
+ verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
}
@Test
public void testAttachToWindowToken() throws RemoteException {
- mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+ doReturn(null).when(mWindowManagerService).attachWindowContextToWindowToken(
+ any(), any(), any());
+ assertFalse(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
verify(mWindowManagerService).attachWindowContextToWindowToken(
ActivityThread.currentActivityThread().getApplicationThread(), mWindowTokenClient,
mWindowToken);
+ verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
+
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToWindowToken(any(), any(), any());
+
+ assertTrue(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
+ verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
}
@Test
@@ -183,35 +184,59 @@
verify(mWindowManagerService, never()).detachWindowContext(any());
+ doReturn(null).when(mWindowManagerService).attachWindowContextToWindowToken(
+ any(), any(), any());
mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
mController.detachIfNeeded(mWindowTokenClient);
- verify(mWindowManagerService).detachWindowContext(any());
+ verify(mWindowManagerService, never()).detachWindowContext(any());
+
+ doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToWindowToken(
+ any(), any(), any());
+ mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+ mController.detachIfNeeded(mWindowTokenClient);
+
+ verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
}
@Test
- public void testOnWindowContextConfigurationChanged() {
- mController.onWindowContextConfigurationChanged(
- mClientToken, mConfiguration, DEFAULT_DISPLAY);
+ public void testOnWindowContextInfoChanged() throws RemoteException {
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToWindowToken(any(), any(), any());
+
+ // No invoke if not attached.
+ mController.onWindowContextInfoChanged(mClientToken, mWindowContextInfo);
verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt());
- mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+ // Invoke postOnConfigurationChanged when attached
+ assertTrue(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
- mController.onWindowContextConfigurationChanged(
- mClientToken, mConfiguration, DEFAULT_DISPLAY);
+ verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
- verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
+ // Invoke onConfigurationChanged when onWindowContextInfoChanged
+ mController.onWindowContextInfoChanged(
+ mClientToken, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1));
+
+ verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
}
@Test
- public void testOnWindowContextWindowRemoved() {
+ public void testOnWindowContextWindowRemoved() throws RemoteException {
+ doReturn(mWindowContextInfo).when(mWindowManagerService)
+ .attachWindowContextToWindowToken(any(), any(), any());
+
+ // No invoke if not attached.
mController.onWindowContextWindowRemoved(mClientToken);
verify(mWindowTokenClient, never()).onWindowTokenRemoved();
+ // No invoke if not onWindowTokenRemoved.
mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+ verify(mWindowTokenClient, never()).onWindowTokenRemoved();
+
+ // Invoke onWindowTokenRemoved when onWindowContextWindowRemoved
mController.onWindowContextWindowRemoved(mClientToken);
verify(mWindowTokenClient).onWindowTokenRemoved();
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 3231192..8268077 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -45,7 +45,6 @@
VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
assertEquals(-1, prebaked.getDuration());
- assertTrue(prebaked.hasNonZeroAmplitude());
assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId());
assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength());
assertTrue(prebaked.shouldFallback());
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 955d6ac..6f5adcd 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -46,7 +46,6 @@
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
assertEquals(-1, primitive.getDuration());
- assertTrue(primitive.hasNonZeroAmplitude());
assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId());
assertEquals(10, primitive.getDelay());
assertEquals(1f, primitive.getScale(), TOLERANCE);
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index dcbb56e..68870e5 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -54,7 +54,6 @@
/* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 100);
assertEquals(100L, ramp.getDuration());
- assertTrue(ramp.hasNonZeroAmplitude());
assertEquals(1f, ramp.getStartAmplitude());
assertEquals(0f, ramp.getEndAmplitude());
assertEquals(100f, ramp.getStartFrequencyHz());
@@ -96,13 +95,6 @@
}
@Test
- public void testHasNonZeroAmplitude() {
- assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude());
- assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude());
- assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude());
- }
-
- @Test
public void testResolve() {
RampSegment ramp = new RampSegment(0, 1, 0, 0, 0);
assertSame(ramp, ramp.resolve(100));
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index f9f1c08..34bb892 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -52,7 +52,6 @@
/* duration= */ 100);
assertEquals(100, step.getDuration());
- assertTrue(step.hasNonZeroAmplitude());
assertEquals(1f, step.getAmplitude());
assertEquals(1f, step.getFrequencyHz());
}
@@ -87,14 +86,6 @@
}
@Test
- public void testHasNonZeroAmplitude() {
- assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude());
- assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude());
- assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude());
- assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude());
- }
-
- @Test
public void testResolve() {
StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 43683ff..c6a9033 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -87,5 +87,6 @@
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
<permission name="android.permission.READ_SEARCH_INDEXABLES" />
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <permission name="android.permission.QUERY_CLONED_APPS"/>
</privapp-permissions>
</permissions>
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 8bd500e..e9abc7e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -150,6 +150,7 @@
],
static_libs: [
"androidx.appcompat_appcompat",
+ "androidx.core_core-animation",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 798250d..26edd7d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -117,6 +117,20 @@
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
*/
+ public void apply(androidx.core.animation.Animator animator,
+ float currValue, float endValue, float velocity) {
+ apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ */
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity) {
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
@@ -152,6 +166,24 @@
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
+ public void apply(androidx.core.animation.Animator animator,
+ float currValue, float endValue, float velocity, float maxDistance) {
+ AnimatorProperties properties = getProperties(currValue, endValue, velocity, maxDistance);
+ animator.setDuration(properties.mDuration);
+ animator.setInterpolator(properties.getInterpolator());
+ }
+
+ /**
+ * Applies the interpolator and length to the animator, such that the fling animation is
+ * consistent with the finger motion.
+ *
+ * @param animator the animator to apply
+ * @param currValue the current value
+ * @param endValue the end value of the animator
+ * @param velocity the current velocity of the motion
+ * @param maxDistance the maximum distance for this interaction; the maximum animation length
+ * gets multiplied by the ratio between the actual distance and this value
+ */
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
@@ -367,6 +399,11 @@
private static class AnimatorProperties {
Interpolator mInterpolator;
long mDuration;
+
+ /** Get an AndroidX interpolator wrapper of the current mInterpolator */
+ public androidx.core.animation.Interpolator getInterpolator() {
+ return mInterpolator::getInterpolation;
+ }
}
/** Builder for {@link #FlingAnimationUtils}. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e69825f2..66b9ade6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -821,13 +821,17 @@
* @param interceptBack whether back should be intercepted or not.
*/
void updateWindowFlagsForBackpress(boolean interceptBack) {
- if (mStackView != null && mAddedToWindowManager) {
+ if (mAddedToWindowManager) {
mWmLayoutParams.flags = interceptBack
? 0
: WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
- mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ if (mStackView != null) {
+ mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+ } else if (mLayerView != null) {
+ mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams);
+ }
}
}
@@ -1053,6 +1057,20 @@
mBubbleData.setExpanded(false /* expanded */);
}
+ /**
+ * Update expanded state when a single bubble is dragged in Launcher.
+ * Will be called only when bubble bar is expanded.
+ * @param bubbleKey key of the bubble to collapse/expand
+ * @param isBeingDragged whether the bubble is being dragged
+ */
+ public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+ if (mBubbleData.getSelectedBubble() != null
+ && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) {
+ // Should collapse/expand only if equals to selected bubble.
+ mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged);
+ }
+ }
+
@VisibleForTesting
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1438,6 +1456,17 @@
}
}
+ /**
+ * Removes all the bubbles.
+ * <p>
+ * Must be called from the main thread.
+ */
+ @VisibleForTesting
+ @MainThread
+ public void removeAllBubbles(@Bubbles.DismissReason int reason) {
+ mBubbleData.dismissAll(reason);
+ }
+
private void onEntryAdded(BubbleEntry entry) {
if (canLaunchInTaskView(mContext, entry)) {
updateBubble(entry);
@@ -2099,14 +2128,25 @@
}
@Override
- public void removeBubble(String key, int reason) {
- // TODO (b/271466616) allow removals from launcher
+ public void removeBubble(String key) {
+ mMainExecutor.execute(
+ () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE));
+ }
+
+ @Override
+ public void removeAllBubbles() {
+ mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE));
}
@Override
public void collapseBubbles() {
mMainExecutor.execute(() -> mController.collapseStack());
}
+
+ @Override
+ public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+ mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index 896a334..df12999 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -103,7 +103,8 @@
* Removes all entities that don't have a user in the activeUsers list, if any entities were
* removed it persists the new list to disk.
*/
- private fun filterForActiveUsersAndPersist(
+ @VisibleForTesting
+ fun filterForActiveUsersAndPersist(
activeUsers: List<Int>,
entitiesByUser: SparseArray<List<BubbleEntity>>
): SparseArray<List<BubbleEntity>> {
@@ -167,7 +168,8 @@
* Job C resumes and reaches yield() and is then cancelled
* Job D resumes and performs another blocking I/O
*/
- private fun persistToDisk(
+ @VisibleForTesting
+ fun persistToDisk(
entitiesByUser: SparseArray<List<BubbleEntity>> = volatileRepository.bubbles
) {
val prev = job
@@ -188,7 +190,6 @@
* bubbles.
*/
@SuppressLint("WrongConstant")
- @VisibleForTesting
fun loadBubbles(
userId: Int,
currentUsers: List<Int>,
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 282db9e..f58b121 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
@@ -108,8 +108,10 @@
public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ // LINT.IfChange
public static final boolean ENABLE_FLING_TO_DISMISS_BUBBLE =
SystemProperties.getBoolean("persist.wm.debug.fling_to_dismiss_bubble", true);
+ // LINT.ThenChange(com/android/launcher3/taskbar/bubbles/BubbleDismissController.java)
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 351319f..4dda068 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -31,8 +31,12 @@
oneway void showBubble(in String key, in int bubbleBarOffsetX, in int bubbleBarOffsetY) = 3;
- oneway void removeBubble(in String key, in int reason) = 4;
+ oneway void removeBubble(in String key) = 4;
- oneway void collapseBubbles() = 5;
+ oneway void removeAllBubbles() = 5;
+
+ oneway void collapseBubbles() = 6;
+
+ oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index b3602b3..689323b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -215,6 +215,14 @@
mExpandedViewAlphaAnimator.reverse();
}
+ /**
+ * Cancel current animations
+ */
+ public void cancelAnimations() {
+ PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
+ mExpandedViewAlphaAnimator.cancel();
+ }
+
private void updateExpandedView() {
if (mExpandedBubble == null || mExpandedBubble.getBubbleBarExpandedView() == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 8ead18b..bc04bfc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -150,6 +150,12 @@
mExpandedView = null;
}
if (mExpandedView == null) {
+ if (expandedView.getParent() != null) {
+ // Expanded view might be animating collapse and is still attached
+ // Cancel current animations and remove from parent
+ mAnimationHelper.cancelAnimations();
+ removeView(expandedView);
+ }
mExpandedBubble = b;
mExpandedView = expandedView;
boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 76ca68b..517f9f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -54,6 +54,15 @@
public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_change_display", false);
+
+ /**
+ * Flag to indicate that desktop stashing is enabled.
+ * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
+ * will be added to the desktop.
+ */
+ private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_stashing", false);
+
/**
* Return {@code true} if desktop mode support is enabled
*/
@@ -84,6 +93,13 @@
}
/**
+ * Return {@code true} if desktop task stashing is enabled when going home.
+ * Allows users to use home screen to add tasks to desktop.
+ */
+ public static boolean isStashingEnabled() {
+ return IS_STASHING_ENABLED;
+ }
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f8d7b6b..b15fd91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -150,20 +150,24 @@
* back to front during the launch.
*/
fun stashDesktopApps(displayId: Int) {
- KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
- desktopModeTaskRepository.setStashed(displayId, true)
+ if (DesktopModeStatus.isStashingEnabled()) {
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
+ desktopModeTaskRepository.setStashed(displayId, true)
+ }
}
/**
* Clear the stashed state for the given display
*/
fun hideStashedDesktopApps(displayId: Int) {
- KtProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: hideStashedApps displayId=%d",
- displayId
- )
- desktopModeTaskRepository.setStashed(displayId, false)
+ if (DesktopModeStatus.isStashingEnabled()) {
+ KtProtoLog.v(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: hideStashedApps displayId=%d",
+ displayId
+ )
+ desktopModeTaskRepository.setStashed(displayId, false)
+ }
}
/** Get number of tasks that are marked as visible */
@@ -172,9 +176,13 @@
}
/** Move a task with given `taskId` to desktop */
- fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) {
+ fun moveToDesktop(
+ decor: DesktopModeWindowDecoration,
+ taskId: Int,
+ wct: WindowContainerTransaction = WindowContainerTransaction()
+ ) {
shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
- task -> moveToDesktop(task, wct)
+ task -> moveToDesktop(decor, task, wct)
}
}
@@ -182,6 +190,7 @@
* Move a task to desktop
*/
fun moveToDesktop(
+ decor: DesktopModeWindowDecoration,
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
@@ -195,7 +204,7 @@
addMoveToDesktopChanges(wct, task)
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+ enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
} else {
shellTaskOrganizer.applyTransaction(wct)
}
@@ -730,7 +739,7 @@
*
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
- * @param y the Y position of the motion event.
+ * @param y the Y position of the top edge of the task
* @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 22929c76..16b2393 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -20,6 +20,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.graphics.PointF;
@@ -36,6 +37,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
import java.util.ArrayList;
@@ -60,6 +62,7 @@
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private MoveToDesktopAnimator mMoveToDesktopAnimator;
+ private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -128,6 +131,18 @@
onAnimationEndCallback);
}
+ /**
+ * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
+ * @param wct WindowContainerTransaction for transition
+ * @param decor {@link DesktopModeWindowDecoration} of task being animated
+ */
+ public void moveToDesktop(@NonNull WindowContainerTransaction wct,
+ DesktopModeWindowDecoration decor) {
+ mDesktopModeWindowDecoration = decor;
+ startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
+ null /* onAnimationEndCallback */);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@@ -167,138 +182,209 @@
}
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+ if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
- // to null and we don't require an animation
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, null);
-
- if (mMoveToDesktopAnimator == null
- || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
- Slog.e(TAG, "No animator available for this transition");
- return false;
- }
-
- // Calculate and set position of the task
- final PointF position = mMoveToDesktopAnimator.getPosition();
- startT.setPosition(sc, position.x, position.y);
- finishT.setPosition(sc, position.x, position.y);
-
- startT.apply();
-
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
-
- return true;
+ return animateMoveToDesktop(change, startT, finishCallback);
}
- Rect endBounds = change.getEndAbsBounds();
+ if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
+ }
+
+ final Rect endBounds = change.getEndAbsBounds();
if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
&& !endBounds.isEmpty()) {
- // This Transition animates a task to freeform bounds after being dragged into freeform
- // mode and brings the remaining freeform tasks to front
- final SurfaceControl sc = change.getLeash();
- startT.setWindowCrop(sc, endBounds.width(),
- endBounds.height());
- startT.apply();
-
- // End the animation that shrinks the window when task is first dragged from fullscreen
- if (mMoveToDesktopAnimator != null) {
- mMoveToDesktopAnimator.endAnimator();
- }
-
- // We want to find the scale of the current bounds relative to the end bounds. The
- // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
- // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
- // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
- final ValueAnimator animator =
- ValueAnimator.ofFloat(
- MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
- animator.addUpdateListener(animation -> {
- final float animationValue = (float) animation.getAnimatedValue();
- t.setScale(sc, animationValue, animationValue);
-
- final float animationWidth = endBounds.width() * animationValue;
- final float animationHeight = endBounds.height() * animationValue;
- final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
- final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
-
- t.setPosition(sc, animationX, animationY);
- t.apply();
- });
-
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
- }
- });
-
- animator.start();
- return true;
+ return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
+ endBounds);
}
if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- // This Transition animates a task to fullscreen after being dragged from the status
- // bar and then released back into the status bar area
- final SurfaceControl sc = change.getLeash();
- // Hide the first (fullscreen) frame because the animation will start from the smaller
- // scale size.
- startT.hide(sc)
- .setWindowCrop(sc, endBounds.width(), endBounds.height())
- .apply();
-
- if (mMoveToDesktopAnimator == null
- || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
- Slog.e(TAG, "No animator available for this transition");
- return false;
- }
-
- // End the animation that shrinks the window when task is first dragged from fullscreen
- mMoveToDesktopAnimator.endAnimator();
-
- final ValueAnimator animator = new ValueAnimator();
- animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
- animator.setDuration(FREEFORM_ANIMATION_DURATION);
- final SurfaceControl.Transaction t = mTransactionSupplier.get();
-
- // Get position of the task
- final float x = mMoveToDesktopAnimator.getPosition().x;
- final float y = mMoveToDesktopAnimator.getPosition().y;
-
- animator.addUpdateListener(animation -> {
- final float scale = (float) animation.getAnimatedValue();
- t.setPosition(sc, x * (1 - scale), y * (1 - scale))
- .setScale(sc, scale, scale)
- .show(sc)
- .apply();
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
- }
- mTransitions.getMainExecutor().execute(
- () -> finishCallback.onTransitionFinished(null, null));
- }
- });
- animator.start();
- return true;
+ return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
+ endBounds);
}
return false;
}
+ private boolean animateMoveToDesktop(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ if (mDesktopModeWindowDecoration == null) {
+ Slog.e(TAG, "Window Decoration is not available for this transition");
+ return false;
+ }
+
+ final SurfaceControl leash = change.getLeash();
+ final Rect startBounds = change.getStartAbsBounds();
+ startT.setPosition(leash, startBounds.left, startBounds.right)
+ .setWindowCrop(leash, startBounds.width(), startBounds.height())
+ .show(leash);
+ mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
+
+ final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
+ change.getStartAbsBounds(), change.getEndAbsBounds());
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final Rect animationValue = (Rect) animator.getAnimatedValue();
+ t.setPosition(leash, animationValue.left, animationValue.right)
+ .setWindowCrop(leash, animationValue.width(), animationValue.height())
+ .show(leash);
+ mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDesktopModeWindowDecoration.hideResizeVeil();
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
+ private boolean animateStartDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
+ // to null and we don't require an animation
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, null);
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // Calculate and set position of the task
+ final PointF position = mMoveToDesktopAnimator.getPosition();
+ startT.setPosition(sc, position.x, position.y);
+ finishT.setPosition(sc, position.x, position.y);
+
+ startT.apply();
+
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+
+ return true;
+ }
+
+ private boolean animateFinalizeDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Rect endBounds) {
+ // This Transition animates a task to freeform bounds after being dragged into freeform
+ // mode and brings the remaining freeform tasks to front
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, endBounds.width(),
+ endBounds.height());
+ startT.apply();
+
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ if (mMoveToDesktopAnimator != null) {
+ mMoveToDesktopAnimator.endAnimator();
+ }
+
+ // We want to find the scale of the current bounds relative to the end bounds. The
+ // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
+ // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
+ // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(
+ MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final float animationValue = (float) animation.getAnimatedValue();
+ t.setScale(sc, animationValue, animationValue);
+
+ final float animationWidth = endBounds.width() * animationValue;
+ final float animationHeight = endBounds.height() * animationValue;
+ final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
+ final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
+
+ t.setPosition(sc, animationX, animationY);
+ t.apply();
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+
+ animator.start();
+ return true;
+ }
+ private boolean animateCancelDragToDesktopMode(
+ @NonNull TransitionInfo.Change change,
+ @NonNull SurfaceControl.Transaction startT,
+ @NonNull SurfaceControl.Transaction finishT,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull Rect endBounds) {
+ // This Transition animates a task to fullscreen after being dragged from the status
+ // bar and then released back into the status bar area
+ final SurfaceControl sc = change.getLeash();
+ // Hide the first (fullscreen) frame because the animation will start from the smaller
+ // scale size.
+ startT.hide(sc)
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .apply();
+
+ if (mMoveToDesktopAnimator == null
+ || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+ Slog.e(TAG, "No animator available for this transition");
+ return false;
+ }
+
+ // End the animation that shrinks the window when task is first dragged from fullscreen
+ mMoveToDesktopAnimator.endAnimator();
+
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+ // Get position of the task
+ final float x = mMoveToDesktopAnimator.getPosition().x;
+ final float y = mMoveToDesktopAnimator.getPosition().y;
+
+ animator.addUpdateListener(animation -> {
+ final float scale = (float) animation.getAnimatedValue();
+ t.setPosition(sc, x * (1 - scale), y * (1 - scale))
+ .setScale(sc, scale, scale)
+ .show(sc)
+ .apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinishedCallback != null) {
+ mOnAnimationFinishedCallback.accept(finishT);
+ }
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5b9e47f..7d82dc17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -352,6 +352,10 @@
mMainExecutor.executeDelayed(
mMovePipInResponseToKeepClearAreasChangeCallback,
PIP_KEEP_CLEAR_AREAS_DELAY);
+
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+ restricted, unrestricted);
}
}
}
@@ -950,6 +954,8 @@
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
if (visible) {
Rect rect = new Rect(
0, mPipBoundsState.getDisplayBounds().bottom - height,
@@ -1007,9 +1013,10 @@
int launcherRotation, Rect hotseatKeepClearArea) {
if (mEnablePipKeepClearAlgorithm) {
- // pre-emptively add the keep clear area for Hotseat, so that it is taken into account
+ // preemptively add the keep clear area for Hotseat, so that it is taken into account
// when calculating the entry destination bounds of PiP window
- mPipBoundsState.getRestrictedKeepClearAreas().add(hotseatKeepClearArea);
+ mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG,
+ hotseatKeepClearArea);
} else {
int shelfHeight = hotseatKeepClearArea.height();
setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e45dacf..e2dce88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -168,6 +168,9 @@
public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
WindowManager.TRANSIT_FIRST_CUSTOM + 14;
+ /** Transition to animate task to desktop. */
+ public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2d7e6a6..4cc755b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -222,7 +222,8 @@
&& (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
|| info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
- || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
+ || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+ || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
}
@@ -356,7 +357,8 @@
// App sometimes draws before the insets from WindowDecoration#relayout have
// been added, so they must be added here
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
+ decoration.incrementRelayoutBlock();
+ mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
@@ -429,10 +431,10 @@
mDragPointerId = e.getPointerId(0);
}
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
- decoration.mTaskSurface, e.getRawY(dragPointerIdx)));
- mDragPositioningCallback.onDragPositioningMove(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ decoration.mTaskSurface, newTaskBounds.top));
mIsDragging = true;
mShouldClick = false;
return true;
@@ -458,10 +460,10 @@
final Point position = new Point(
(int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)),
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
- mDragPositioningCallback.onDragPositioningEnd(
+ final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
+ position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId)));
mIsDragging = false;
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 941617d..1669cf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor;
import android.annotation.IntDef;
+import android.graphics.Rect;
/**
* Callback called when receiving drag-resize or drag-move related input events.
@@ -46,13 +47,15 @@
* Called when the pointer moves during a drag-resize or drag-move.
* @param x x coordinate in window decoration coordinate system of the new pointer location
* @param y y coordinate in window decoration coordinate system of the new pointer location
+ * @return the updated task bounds
*/
- void onDragPositioningMove(float x, float y);
+ Rect onDragPositioningMove(float x, float y);
/**
* Called when a drag-resize or drag-move stops.
* @param x x coordinate in window decoration coordinate system where the drag resize stops
* @param y y coordinate in window decoration coordinate system where the drag resize stops
+ * @return the final bounds for the dragged task
*/
- void onDragPositioningEnd(float x, float y);
+ Rect onDragPositioningEnd(float x, float y);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index e1b6db5..e0ee252 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -85,7 +85,7 @@
}
@Override
- public void onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
@@ -106,10 +106,11 @@
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
t.apply();
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
- public void onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(float x, float y) {
// If task has been resized or task was dragged into area outside of
// mDisallowedAreaForEndBounds, apply WCT to finish it.
if (isResizing() && mHasDragResized) {
@@ -136,6 +137,7 @@
mRepositionStartPoint.set(0, 0);
mCtrlType = CTRL_TYPE_UNDEFINED;
mHasDragResized = false;
+ return new Rect(mRepositionTaskBounds);
}
private boolean isResizing() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index ae3b5eb..fb05c69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -105,7 +105,7 @@
}
@Override
- public void onDragPositioningMove(float x, float y) {
+ public Rect onDragPositioningMove(float x, float y) {
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
@@ -117,10 +117,11 @@
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
t.apply();
}
+ return new Rect(mRepositionTaskBounds);
}
@Override
- public void onDragPositioningEnd(float x, float y) {
+ public Rect onDragPositioningEnd(float x, float y) {
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
mRepositionStartPoint);
if (isResizing()) {
@@ -151,6 +152,7 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
+ return new Rect(mRepositionTaskBounds);
}
private boolean isResizing() {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 0947723..dfbadae 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -51,14 +51,18 @@
}
filegroup {
- name: "WMShellFlickerTestsSplitScreenEnter-src",
+ name: "WMShellFlickerTestsSplitScreenGroup1-src",
srcs: [
- "src/com/android/wm/shell/flicker/splitscreen/Enter*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/A*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/B*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/C*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/D*.kt",
+ "src/com/android/wm/shell/flicker/splitscreen/E*.kt",
],
}
filegroup {
- name: "WMShellFlickerTestsSplitScreenOther-src",
+ name: "WMShellFlickerTestsSplitScreenGroup2-src",
srcs: [
"src/com/android/wm/shell/flicker/splitscreen/*.kt",
],
@@ -135,8 +139,8 @@
exclude_srcs: [
":WMShellFlickerTestsBubbles-src",
":WMShellFlickerTestsPip-src",
- ":WMShellFlickerTestsSplitScreenEnter-src",
- ":WMShellFlickerTestsSplitScreenOther-src",
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
":WMShellFlickerTestsSplitScreenBase-src",
":WMShellFlickerServiceTests-src",
],
@@ -167,7 +171,7 @@
}
android_test {
- name: "WMShellFlickerTestsSplitScreenEnter",
+ name: "WMShellFlickerTestsSplitScreenGroup1",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
package_name: "com.android.wm.shell.flicker.splitscreen",
@@ -175,12 +179,12 @@
srcs: [
":WMShellFlickerTestsBase-src",
":WMShellFlickerTestsSplitScreenBase-src",
- ":WMShellFlickerTestsSplitScreenEnter-src",
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
],
}
android_test {
- name: "WMShellFlickerTestsSplitScreenOther",
+ name: "WMShellFlickerTestsSplitScreenGroup2",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
package_name: "com.android.wm.shell.flicker.splitscreen",
@@ -188,10 +192,10 @@
srcs: [
":WMShellFlickerTestsBase-src",
":WMShellFlickerTestsSplitScreenBase-src",
- ":WMShellFlickerTestsSplitScreenOther-src",
+ ":WMShellFlickerTestsSplitScreenGroup2-src",
],
exclude_srcs: [
- ":WMShellFlickerTestsSplitScreenEnter-src",
+ ":WMShellFlickerTestsSplitScreenGroup1-src",
],
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 13aa758..5efa51b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -63,10 +63,12 @@
}
/** Checks that the visible region of [pipApp] window always moves down during the animation. */
- @FlakyTest(bugId = 292813143) @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+ @Presubmit
+ @Test
+ fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
/** Checks that the visible region of [pipApp] layer always moves down during the animation. */
- @FlakyTest(bugId = 292813143)
+ @Presubmit
@Test
fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 851391d..10dceba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -16,9 +16,13 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import android.tools.common.NavBar
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,6 +61,21 @@
}
@Test
+ @FlakyTest(bugId = 293578017)
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ // TODO(b/293578017) remove once that bug is resolve
+ @Test
+ @Presubmit
+ fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() =
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry(
+ LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(
+ WALLPAPER_BBQ_WRAPPER
+ )
+ ) }
+
+ @Test
fun splitScreenDividerIsVisibleAtEnd() {
flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
index e5c124c..f1cb37e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/CommonAssertions.kt
@@ -265,6 +265,7 @@
val dividerRegion =
layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+ visibleRegion(component).isNotEmpty()
visibleRegion(component)
.coversAtMost(
if (displayBounds.width > displayBounds.height) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index 6d9d62d..0e05e01 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -18,18 +18,22 @@
import android.app.ActivityTaskManager
import android.content.pm.LauncherApps
-import android.content.pm.ShortcutInfo
+import android.os.Handler
+import android.os.Looper
import android.util.SparseArray
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.bubbles.storage.BubbleEntity
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
-import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.HandlerExecutor
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
+import org.mockito.Mockito
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
class BubbleDataRepositoryTest : ShellTestCase() {
@@ -118,7 +122,8 @@
)
)
- private val mainExecutor = mock(ShellExecutor::class.java)
+ private val testHandler = Handler(Looper.getMainLooper())
+ private val mainExecutor = HandlerExecutor(testHandler)
private val launcherApps = mock(LauncherApps::class.java)
private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -128,13 +133,11 @@
@Before
fun setup() {
- persistentRepository = spy(BubblePersistentRepository(mContext))
- dataRepository = BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)
+ persistentRepository = BubblePersistentRepository(mContext)
+ dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
- // Add the bubbles to the persistent repository
persistedBubbles.put(0, user0BubbleEntities)
persistedBubbles.put(1, user1BubbleEntities)
- persistentRepository.persistsToDisk(persistedBubbles)
}
@After
@@ -144,47 +147,58 @@
}
@Test
- fun testLoadBubbles_invalidParent() {
- val activeUserIds = listOf(10, 1, 12) // Missing user 0 in persistedBubbles
- dataRepository.loadBubbles(1, activeUserIds) {
- // Verify that user 0 has been removed from the persisted list
- val entitiesByUser = persistentRepository.readFromDisk()
- assertThat(entitiesByUser.get(0)).isNull()
- }
+ fun testFilterForActiveUsersAndPersist_allValid() {
+ // Matches all the users in user0BubbleEntities & user1BubbleEntities
+ val activeUserIds = listOf(0, 10, 1, 12)
+
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
+
+ // No invalid users, so no changes
+ assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
+
+ // No invalid users, so no persist to disk happened
+ verify(dataRepository, never()).persistToDisk(
+ any(SparseArray<List<BubbleEntity>>()::class.java))
}
@Test
- fun testLoadBubbles_invalidChild() {
+ fun testFilterForActiveUsersAndPersist_invalidParent() {
+ // When we start, we do have user 0 bubbles.
+ assertThat(persistedBubbles.get(0)).isNotEmpty()
+
+ val activeUserIds = listOf(10, 1, 12) // Missing user 0
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
+
+ // We no longer have any user 0 bubbles.
+ assertThat(validEntitiesByUser.get(0)).isNull()
+ // User 1 bubbles should be the same.
+ assertThat(validEntitiesByUser.get(1)).isEqualTo(user1BubbleEntities)
+
+ // Verify that persist to disk happened with the new valid entities list.
+ verify(dataRepository).persistToDisk(validEntitiesByUser)
+ }
+
+ @Test
+ fun testFilterForActiveUsersAndPersist_invalidChild() {
+ // Build a list to compare against (remove all user 12 bubbles)
+ val (user1EntitiesWithUser12, user1EntitiesWithoutUser12) =
+ user1BubbleEntities.partition { it.userId == 12 }
+
+ // Verify we start with user 12 bubbles
+ assertThat(persistedBubbles.get(1).containsAll(user1EntitiesWithUser12)).isTrue()
+
val activeUserIds = listOf(0, 10, 1) // Missing user 1's child user 12
- dataRepository.loadBubbles(1, activeUserIds) {
- // Build a list to compare against
- val user1BubblesWithoutUser12 = mutableListOf<Bubble>()
- val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>()
- for (entity in user1BubbleEntities) {
- if (entity.userId != 12) {
- user1BubblesWithoutUser12.add(entity.toBubble())
- user1EntitiesWithoutUser12.add(entity)
- }
- }
+ val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+ activeUserIds, persistedBubbles)
- // Verify that user 12 has been removed from the persisted list
- val entitiesByUser = persistentRepository.readFromDisk()
- assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
- }
+ // We no longer have any user 12 bubbles.
+ assertThat(validEntitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
+
+ // Verify that persist to disk happened with the new valid entities list.
+ verify(dataRepository).persistToDisk(validEntitiesByUser)
}
- private fun BubbleEntity.toBubble(): Bubble {
- return Bubble(
- key,
- mock(ShortcutInfo::class.java),
- desiredHeight,
- desiredHeightResId,
- title,
- taskId,
- locus,
- isDismissable,
- mainExecutor,
- mock(Bubbles.BubbleMetadataFlagListener::class.java)
- )
- }
+ fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 1477cf7..5d87cf8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -57,6 +57,7 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.After
@@ -92,6 +93,7 @@
@Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
+ @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -276,8 +278,8 @@
fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
- controller.moveToDesktop(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_FREEFORM)
}
@@ -286,15 +288,15 @@
fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
- controller.moveToDesktop(task)
- val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
.isEqualTo(WINDOWING_MODE_UNDEFINED)
}
@Test
fun moveToDesktop_nonExistentTask_doesNothing() {
- controller.moveToDesktop(999)
+ controller.moveToDesktop(desktopModeWindowDecoration, 999)
verifyWCTNotExecuted()
}
@@ -305,9 +307,9 @@
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
- controller.moveToDesktop(fullscreenTask)
+ controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestMoveToDesktopWct()) {
// Operations should include home task, freeform task
assertThat(hierarchyOps).hasSize(3)
assertReorderSequence(homeTask, freeformTask, fullscreenTask)
@@ -327,9 +329,9 @@
val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
markTaskHidden(freeformTaskSecond)
- controller.moveToDesktop(fullscreenTaskDefault)
+ controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
- with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+ with(getLatestMoveToDesktopWct()) {
// Check that hierarchy operations do not include tasks from second display
assertThat(hierarchyOps.map { it.container })
.doesNotContain(homeTaskSecond.token.asBinder())
@@ -498,6 +500,7 @@
@Test
fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
markTaskHidden(stashedFreeformTask)
@@ -569,6 +572,7 @@
@Test
fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
markTaskHidden(stashedFreeformTask)
@@ -626,6 +630,8 @@
@Test
fun stashDesktopApps_stateUpdates() {
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
controller.stashDesktopApps(DEFAULT_DISPLAY)
assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
@@ -634,6 +640,8 @@
@Test
fun hideStashedDesktopApps_stateUpdates() {
+ whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
@@ -715,6 +723,16 @@
return arg.value
}
+ private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
+ val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ if (ENABLE_SHELL_TRANSITIONS) {
+ verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+ } else {
+ verify(shellTaskOrganizer).applyTransaction(arg.capture())
+ }
+ return arg.value
+ }
+
private fun verifyWCTNotExecuted() {
if (ENABLE_SHELL_TRANSITIONS) {
verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f27a8de..6b67d14 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -460,6 +460,9 @@
* updates} in order to keep the system UI in a consistent state. You can also call this method
* at any other point to update the listing preference dynamically.
*
+ * <p>Any calls to this method from a privileged router will throw an {@link
+ * UnsupportedOperationException}.
+ *
* <p>Notes:
*
* <ol>
@@ -476,24 +479,7 @@
* route listing. When null, the system uses its default listing criteria.
*/
public void setRouteListingPreference(@Nullable RouteListingPreference routeListingPreference) {
- synchronized (mLock) {
- if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
- // Nothing changed. We return early to save a call to the system server.
- return;
- }
- mRouteListingPreference = routeListingPreference;
- try {
- if (mStub == null) {
- MediaRouter2Stub stub = new MediaRouter2Stub();
- mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
- mStub = stub;
- }
- mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
- notifyRouteListingPreferenceUpdated(routeListingPreference);
- }
+ mImpl.setRouteListingPreference(routeListingPreference);
}
/**
@@ -1962,6 +1948,8 @@
void unregisterRouteCallback();
+ void setRouteListingPreference(@Nullable RouteListingPreference preference);
+
List<MediaRoute2Info> getAllRoutes();
void setOnGetControllerHintsListener(OnGetControllerHintsListener listener);
@@ -2102,6 +2090,12 @@
// Do nothing.
}
+ @Override
+ public void setRouteListingPreference(@Nullable RouteListingPreference preference) {
+ throw new UnsupportedOperationException(
+ "RouteListingPreference cannot be set by a privileged MediaRouter2 instance.");
+ }
+
/** Gets the list of all discovered routes. */
@Override
public List<MediaRoute2Info> getAllRoutes() {
@@ -2892,6 +2886,28 @@
}
}
+ @Override
+ public void setRouteListingPreference(@Nullable RouteListingPreference preference) {
+ synchronized (mLock) {
+ if (Objects.equals(mRouteListingPreference, preference)) {
+ // Nothing changed. We return early to save a call to the system server.
+ return;
+ }
+ mRouteListingPreference = preference;
+ try {
+ if (mStub == null) {
+ MediaRouter2Stub stub = new MediaRouter2Stub();
+ mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
+ mStub = stub;
+ }
+ mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ notifyRouteListingPreferenceUpdated(preference);
+ }
+ }
+
/**
* Returns {@link Collections#emptyList()}. Local routes can only access routes related to
* their {@link RouteDiscoveryPreference} through {@link #getRoutes()}.
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 0c6096e..6e2aaab 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -130,7 +130,7 @@
/**
* Returns the {@link MidiDeviceInfo} instance for this service
- * @return the MidiDeviceInfo of the virtual MIDI device
+ * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
*/
public final @Nullable MidiDeviceInfo getDeviceInfo() {
return mDeviceInfo;
@@ -140,7 +140,7 @@
* Called to notify when the {@link MidiDeviceStatus} has changed
* @param status the current status of the MIDI device
*/
- public void onDeviceStatusChanged(@Nullable MidiDeviceStatus status) {
+ public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
}
/**
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4193ffa..ac8e4d4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -64,6 +64,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -454,6 +455,7 @@
assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
}
+ @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable.
@Test
public void testRouterRelease_managerGetRoutingSessions() throws Exception {
CountDownLatch transferLatch = new CountDownLatch(1);
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index 6a5d2c0..fd394fc 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -12,6 +12,15 @@
}
],
"file_patterns": ["permission_manager.cpp"]
+ },
+ {
+ "name": "CtsOsTestCases",
+ "options": [
+ {
+ "include-filter": "android.os.cts.PerformanceHintManagerTest"
+ }
+ ],
+ "file_patterns": ["performance_hint.cpp"]
}
]
}
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index b3628fa..6198f40 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -274,9 +274,10 @@
binder::Status ret = mHintManager->setHintSessionThreads(mHintSession, tids);
if (!ret.isOk()) {
ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
- if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY ||
- ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
+ if (ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
return EINVAL;
+ } else if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY) {
+ return EPERM;
}
return EPIPE;
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 791adfd..6f7562b 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -178,4 +178,15 @@
.WillOnce(Return(Status()));
result = APerformanceHint_setThreads(session, newTids.data(), newTids.size());
EXPECT_EQ(0, result);
+
+ testing::Mock::VerifyAndClearExpectations(mMockIHintManager);
+ std::vector<int32_t> invalidTids;
+ auto status = Status::fromExceptionCode(binder::Status::Exception::EX_SECURITY);
+ invalidTids.push_back(4);
+ invalidTids.push_back(6);
+ EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(invalidTids)))
+ .Times(Exactly(1))
+ .WillOnce(Return(status));
+ result = APerformanceHint_setThreads(session, invalidTids.data(), invalidTids.size());
+ EXPECT_EQ(EPERM, result);
}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
index 8639f47..0d1475a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
@@ -16,6 +16,8 @@
package com.android.packageinstaller;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
@@ -186,7 +188,9 @@
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
- context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
+ Intent intentToStart = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ intentToStart.addFlags(FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intentToStart);
return;
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c244ca0..3d35bad 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -58,6 +58,7 @@
"setupdesign",
"zxing-core-1.7",
"androidx.room_room-runtime",
+ "settingslib_flags_lib",
],
@@ -88,3 +89,16 @@
"SettingsLib",
],
}
+
+aconfig_declarations {
+ name: "settingslib_media_flags",
+ package: "com.android.settingslib.media.flags",
+ srcs: [
+ "aconfig/settingslib_media_flag_declarations.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "settingslib_flags_lib",
+ aconfig_declarations: "settingslib_media_flags",
+}
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index 13f8a37..322d6cf 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,6 +18,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.settingslib">
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+
<application>
<activity
android:name="com.android.settingslib.users.AvatarPickerActivity"
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
index 5d6c343..accaa67 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
@@ -25,10 +25,12 @@
<color name="settingslib_color_blue100">#d2e3fc</color>
<color name="settingslib_color_blue50">#e8f0fe</color>
<color name="settingslib_color_green600">#1e8e3e</color>
+ <color name="settingslib_color_green500">#34A853</color>
<color name="settingslib_color_green400">#5bb974</color>
<color name="settingslib_color_green100">#ceead6</color>
<color name="settingslib_color_green50">#e6f4ea</color>
<color name="settingslib_color_red600">#d93025</color>
+ <color name="settingslib_color_red500">#B3261E</color>
<color name="settingslib_color_red400">#ee675c</color>
<color name="settingslib_color_red100">#fad2cf</color>
<color name="settingslib_color_red50">#fce8e6</color>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 5e2c437..f166a18 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -42,9 +42,6 @@
".grey600",
R.color.settingslib_color_grey400);
map.put(
- ".grey700",
- R.color.settingslib_color_grey500);
- map.put(
".grey800",
R.color.settingslib_color_grey300);
map.put(
@@ -62,6 +59,12 @@
map.put(
".green400",
R.color.settingslib_color_green600);
+ map.put(
+ ".green200",
+ R.color.settingslib_color_green500);
+ map.put(
+ ".red200",
+ R.color.settingslib_color_red500);
DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
}
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 864a8bb..c8df760 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -123,8 +123,7 @@
@Override
public boolean performClick() {
- mSwitch.performClick();
- return super.performClick();
+ return mSwitch.performClick();
}
/**
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9a16df8..ee40b02 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.0.2"
+agp = "8.1.0"
dexmaker-mockito = "2.28.3"
kotlin = "1.8.10"
truth = "1.1"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index e708b1c..c1962a7 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 33f49e3..5b0ac44 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -15,7 +15,7 @@
#
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew
index 4f906e0..aeb74cb 100755
--- a/packages/SettingsLib/Spa/gradlew
+++ b/packages/SettingsLib/Spa/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,98 @@
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +118,9 @@
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +129,7 @@
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +137,109 @@
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
diff --git a/packages/SettingsLib/Spa/gradlew.bat b/packages/SettingsLib/Spa/gradlew.bat
deleted file mode 100644
index 107acd32..0000000
--- a/packages/SettingsLib/Spa/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 79f8c46..7f5948c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -22,7 +22,7 @@
name: "SpaLib",
srcs: ["src/**/*.kt"],
-
+ use_resource_processor: true,
static_libs: [
"androidx.slice_slice-builders",
"androidx.slice_slice-core",
@@ -50,5 +50,6 @@
// Expose the srcs to tests, so the tests can access the internal classes.
filegroup {
name: "SpaLib_srcs",
+ visibility: ["//frameworks/base/packages/SettingsLib/Spa/tests"],
srcs: ["src/**/*.kt"],
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 188e7f6..377e72ed 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,11 +52,11 @@
}
dependencies {
- api("androidx.appcompat:appcompat:1.7.0-alpha02")
+ api("androidx.appcompat:appcompat:1.7.0-alpha03")
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha03")
+ api("androidx.compose.material3:material3:1.2.0-alpha04")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index e4d56cc..65f5d34 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -22,7 +22,7 @@
name: "SpaLibTestUtils",
srcs: ["src/**/*.kt"],
-
+ use_resource_processor: true,
static_libs: [
"SpaLib",
"androidx.arch.core_core-testing",
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 4a7418f..eaeda3c 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -22,7 +22,7 @@
name: "SpaPrivilegedLib",
srcs: ["src/**/*.kt"],
-
+ use_resource_processor: true,
static_libs: [
"SpaLib",
"SettingsLib",
@@ -45,5 +45,6 @@
// Expose the srcs to tests, so the tests can access the internal classes.
filegroup {
name: "SpaPrivilegedLib_srcs",
+ visibility: [":__subpackages__"],
srcs: ["src/**/*.kt"],
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt
new file mode 100644
index 0000000..05cb1b1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.settingslib.spaprivileged.framework.compose
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.R
+
+/** An empty placer holder string. */
+@Composable
+fun placeholder() = stringResource(R.string.summary_placeholder)
+
+/** Gets an empty placer holder string. */
+fun Context.getPlaceholder(): String = getString(R.string.summary_placeholder)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 1a7d896..de2cf1f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -23,11 +23,11 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.compose.rememberContext
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -40,7 +40,7 @@
@Composable
fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
val context = LocalContext.current
- return produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
+ return produceState(initialValue = placeholder(), app) {
withContext(Dispatchers.IO) {
value = if (isClonedAppPage || isCloneApp(context, app)) {
context.getString(R.string.cloned_app_info_label, loadLabel(app))
@@ -82,7 +82,7 @@
withContext(Dispatchers.IO) {
value = when {
context.userManager.isManagedProfile(app.userId) -> {
- context.getString(R.string.category_work)
+ context.getString(com.android.settingslib.R.string.category_work)
}
else -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index fab3ae8..cc3584b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -20,7 +20,7 @@
import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
import android.content.Context
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.R
class EnterpriseRepository(private val context: Context) {
private val resources by lazy {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index ae362c8..e2ff7b0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -27,7 +27,7 @@
import com.android.settingslib.RestrictedLockUtils
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
import com.android.settingslib.RestrictedLockUtilsInternal
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.widget.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
@@ -56,11 +56,15 @@
override fun getSummary(checked: Boolean?) = when (checked) {
true -> enterpriseRepository.getEnterpriseString(
- Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
+ updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+ resId = R.string.enabled_by_admin,
)
+
false -> enterpriseRepository.getEnterpriseString(
- Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin
+ updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+ resId = R.string.disabled_by_admin,
)
+
else -> ""
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index b43210f..cee750e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -72,7 +72,7 @@
private fun InstallType(app: ApplicationInfo) {
if (!app.isInstantApp) return
Spacer(modifier = Modifier.height(4.dp))
- SettingsBody(stringResource(R.string.install_type_instant))
+ SettingsBody(stringResource(com.android.settingslib.widget.R.string.install_type_instant))
}
@Composable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 3e96994..5fc1972 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -24,9 +24,8 @@
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
import com.android.settingslib.spaprivileged.model.app.userHandle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -36,7 +35,7 @@
@Composable
fun ApplicationInfo.getStorageSize(): State<String> {
val context = LocalContext.current
- return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+ return produceState(initialValue = placeholder()) {
withContext(Dispatchers.IO) {
val sizeBytes = calculateSizeBytes(context)
value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index cbc4822..1fa854a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -38,6 +38,7 @@
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.userId
@@ -173,7 +174,7 @@
when (allowed.value) {
true -> context.getString(R.string.app_permission_summary_allowed)
false -> context.getString(R.string.app_permission_summary_not_allowed)
- null -> context.getString(R.string.summary_placeholder)
+ null -> context.getPlaceholder()
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index b08b6df..e77dcd4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -32,7 +32,7 @@
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
@@ -72,9 +72,12 @@
checked: State<Boolean?>,
): State<String> = when (restrictedMode) {
is NoRestricted -> summaryIfNoRestricted
- is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
+ is BaseUserRestricted -> stateOf(
+ context.getString(com.android.settingslib.R.string.disabled)
+ )
+
is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
- null -> stateOf(context.getString(R.string.summary_placeholder))
+ null -> stateOf(context.getPlaceholder())
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
index 26caa01..d11e63a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -25,7 +25,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.delay
-import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -66,7 +65,8 @@
val contentDescription = produceIconContentDescription()
- assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work))
+ assertThat(contentDescription.value)
+ .isEqualTo(context.getString(com.android.settingslib.R.string.category_work))
}
@Test
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index f6f4889..82fbee9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -116,7 +116,7 @@
private fun onMoreOptions() =
composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
+ context.getString(androidx.appcompat.R.string.abc_action_menu_overflow_description)
)
private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 961ec10..457b810 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
@@ -95,9 +96,7 @@
val summaryState = getSummary(listModel)
- assertThat(summaryState.value).isEqualTo(
- context.getString(R.string.summary_placeholder)
- )
+ assertThat(summaryState.value).isEqualTo(context.getPlaceholder())
}
@Test
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
new file mode 100644
index 0000000..0b74fa8
--- /dev/null
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.settingslib.media.flags"
+
+flag {
+ name: "use_media_router2_for_info_media_manager"
+ namespace: "placeholder_namespace"
+ description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
+ bug: "192657812"
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 7e27560..bf63f5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -62,6 +62,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -82,6 +83,13 @@
private static final String TAG = "InfoMediaManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /** Checked exception that signals the specified package is not present in the system. */
+ public static class PackageNotAvailableException extends Exception {
+ public PackageNotAvailableException(String message) {
+ super(message);
+ }
+ }
+
protected String mPackageName;
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
@@ -98,6 +106,28 @@
}
}
+ /** Creates an instance of InfoMediaManager. */
+ public static InfoMediaManager createInstance(
+ Context context,
+ String packageName,
+ Notification notification,
+ LocalBluetoothManager localBluetoothManager) {
+ if (Flags.useMediaRouter2ForInfoMediaManager()) {
+ try {
+ return new RouterInfoMediaManager(
+ context, packageName, notification, localBluetoothManager);
+ } catch (PackageNotAvailableException ex) {
+ // TODO: b/293578081 - Propagate this exception to callers for proper handling.
+ Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
+ return new NoOpInfoMediaManager(
+ context, packageName, notification, localBluetoothManager);
+ }
+ } else {
+ return new ManagerInfoMediaManager(
+ context, packageName, notification, localBluetoothManager);
+ }
+ }
+
@Override
public void startScan() {
mMediaDevices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 4fb0487..fe3ef5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -137,7 +137,7 @@
}
mInfoMediaManager =
- new ManagerInfoMediaManager(
+ InfoMediaManager.createInstance(
context, packageName, notification, mLocalBluetoothManager);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
new file mode 100644
index 0000000..9d578bc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -0,0 +1,154 @@
+/*
+ * 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.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * No-op implementation of {@link InfoMediaManager}.
+ *
+ * <p>This implementation is used when {@link RouterInfoMediaManager} throws a {@link
+ * InfoMediaManager.PackageNotAvailableException}.
+ */
+// TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
+/* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+
+ NoOpInfoMediaManager(
+ Context context,
+ String packageName,
+ Notification notification,
+ LocalBluetoothManager localBluetoothManager) {
+ super(context, packageName, notification, localBluetoothManager);
+ }
+
+ @Override
+ public void stopScan() {
+ // Do nothing.
+ }
+
+ @Override
+ protected void startScanOnRouter() {
+ // Do nothing.
+ }
+
+ @Override
+ protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+ return false;
+ }
+
+ @Override
+ protected void transferToRoute(@NonNull MediaRoute2Info route) {
+ // Do nothing.
+ }
+
+ @Override
+ protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ // Do nothing.
+ }
+
+ @Override
+ protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ // Do nothing.
+ }
+
+ @Override
+ protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+ // Do nothing.
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+ // Do nothing.
+ }
+
+ @Override
+ protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+ // Do nothing.
+ }
+
+ @Nullable
+ @Override
+ protected RouteListingPreference getRouteListingPreference() {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected List<RoutingSessionInfo> getRemoteSessions() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+ return Collections.emptyList();
+ }
+
+ @Nullable
+ @Override
+ protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+ return null;
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getAllRoutes() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+ return Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+ return Collections.emptyList();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
new file mode 100644
index 0000000..70956e9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -0,0 +1,320 @@
+/*
+ * 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.settingslib.media;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
+@SuppressLint("MissingPermission")
+public final class RouterInfoMediaManager extends InfoMediaManager {
+
+ private static final String TAG = "RouterInfoMediaManager";
+
+ private final MediaRouter2 mRouter;
+ private final MediaRouter2Manager mRouterManager;
+
+ private final Executor mExecutor = Executors.newSingleThreadExecutor();
+
+ private final RouteCallback mRouteCallback = new RouteCallback();
+ private final TransferCallback mTransferCallback = new TransferCallback();
+ private final ControllerCallback mControllerCallback = new ControllerCallback();
+ private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
+ new RouteListingPreferenceCallback();
+
+ // TODO: b/192657812 - Create factory method in InfoMediaManager to return
+ // RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+ public RouterInfoMediaManager(
+ Context context,
+ String packageName,
+ Notification notification,
+ LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+ super(context, packageName, notification, localBluetoothManager);
+
+ // TODO: b/291277292 - Change optional package name for a mandatory uid.
+ if (packageName == null) {
+ packageName = context.getPackageName();
+ }
+
+ mRouter = MediaRouter2.getInstance(context, packageName);
+
+ if (mRouter == null) {
+ throw new PackageNotAvailableException(
+ "Package name " + packageName + " does not exist.");
+ }
+ mRouterManager = MediaRouter2Manager.getInstance(context);
+ }
+
+ @Override
+ protected void startScanOnRouter() {
+ mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
+ mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerTransferCallback(mExecutor, mTransferCallback);
+ mRouter.registerControllerCallback(mExecutor, mControllerCallback);
+ mRouter.startScan();
+ }
+
+ @Override
+ public void stopScan() {
+ mRouter.stopScan();
+ mRouter.unregisterControllerCallback(mControllerCallback);
+ mRouter.unregisterTransferCallback(mTransferCallback);
+ mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+ mRouter.unregisterRouteCallback(mRouteCallback);
+ }
+
+ @Override
+ protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+ if (device.mRouteInfo == null) {
+ return false;
+ }
+
+ RoutingController controller = mRouter.getSystemController();
+ mRouter.transfer(controller, device.mRouteInfo);
+ return true;
+ }
+
+ @Override
+ protected void transferToRoute(@NonNull MediaRoute2Info route) {
+ mRouter.transferTo(route);
+ }
+
+ @Override
+ protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ if (controller != null) {
+ controller.selectRoute(route);
+ }
+ }
+
+ @Override
+ protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ if (controller != null) {
+ controller.deselectRoute(route);
+ }
+ }
+
+ @Override
+ protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+ RoutingController controller = getControllerForSession(sessionInfo);
+ if (controller != null) {
+ controller.release();
+ }
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ if (controller == null) {
+ return Collections.emptyList();
+ }
+
+ // Filter out selected routes.
+ List<String> selectedRouteIds = controller.getRoutingSessionInfo().getSelectedRoutes();
+ return controller.getSelectableRoutes().stream()
+ .filter(route -> !selectedRouteIds.contains(route.getId()))
+ .collect(Collectors.toList());
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ if (controller == null) {
+ return Collections.emptyList();
+ }
+
+ return controller.getDeselectableRoutes();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+ RoutingController controller = getControllerForSession(info);
+ if (controller == null) {
+ return Collections.emptyList();
+ }
+ return controller.getSelectedRoutes();
+ }
+
+ @Override
+ protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+ // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
+ // MR2 filters information by package name.
+ mRouterManager.setSessionVolume(info, volume);
+ }
+
+ @Override
+ protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+ mRouter.setRouteVolume(route, volume);
+ }
+
+ @Nullable
+ @Override
+ protected RouteListingPreference getRouteListingPreference() {
+ return mRouter.getRouteListingPreference();
+ }
+
+ @NonNull
+ @Override
+ protected List<RoutingSessionInfo> getRemoteSessions() {
+ // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
+ // MR2 filters information by package name.
+ return mRouterManager.getRemoteSessions();
+ }
+
+ @NonNull
+ @Override
+ protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+ return mRouter.getControllers().stream()
+ .map(RoutingController::getRoutingSessionInfo)
+ .collect(Collectors.toList());
+ }
+
+ @Nullable
+ @Override
+ protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+ // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager calls as
+ // MR2 filters information by package name.
+
+ for (RoutingSessionInfo sessionInfo : getRemoteSessions()) {
+ if (TextUtils.equals(sessionInfo.getId(), sessionId)) {
+ return sessionInfo;
+ }
+ }
+
+ RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null);
+ return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null;
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getAllRoutes() {
+ return mRouter.getAllRoutes();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+ return mRouter.getRoutes();
+ }
+
+ @NonNull
+ @Override
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+ List<MediaRoute2Info> transferableRoutes = new ArrayList<>();
+
+ List<RoutingController> controllers = mRouter.getControllers();
+ RoutingController activeController = controllers.get(controllers.size() - 1);
+ RoutingSessionInfo sessionInfo = activeController.getRoutingSessionInfo();
+ List<MediaRoute2Info> routes = mRouter.getRoutes();
+
+ for (MediaRoute2Info route : routes) {
+ boolean isCrossDeviceTransfer = sessionInfo.isSystemSession() ^ route.isSystemRoute();
+
+ // Always show remote routes if transfer is local -> remote or viceversa regardless of
+ // whether route is in transferable routes list.
+ if (sessionInfo.getTransferableRoutes().contains(route.getId())
+ || isCrossDeviceTransfer) {
+ transferableRoutes.add(route);
+ }
+ }
+
+ return transferableRoutes;
+ }
+
+ @Nullable
+ private RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
+ return mRouter.getController(sessionInfo.getId());
+ }
+
+ private final class RouteCallback extends MediaRouter2.RouteCallback {
+ @Override
+ public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {
+ refreshDevices();
+ }
+
+ @Override
+ public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {
+ refreshDevices();
+ }
+ }
+
+ private final class TransferCallback extends MediaRouter2.TransferCallback {
+ @Override
+ public void onTransfer(
+ @NonNull RoutingController oldController,
+ @NonNull RoutingController newController) {
+ rebuildDeviceList();
+ notifyCurrentConnectedDeviceChanged();
+ }
+
+ @Override
+ public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onStop(@NonNull RoutingController controller) {
+ refreshDevices();
+ }
+
+ @Override
+ public void onRequestFailed(int reason) {
+ dispatchOnRequestFailed(reason);
+ }
+ }
+
+ private final class ControllerCallback extends MediaRouter2.ControllerCallback {
+ @Override
+ public void onControllerUpdated(@NonNull RoutingController controller) {
+ refreshDevices();
+ }
+ }
+
+ private final class RouteListingPreferenceCallback
+ extends MediaRouter2.RouteListingPreferenceCallback {
+ @Override
+ public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
+ notifyRouteListingPreferenceUpdated(preference);
+ refreshDevices();
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ff3eeec..8970deb 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -48,11 +48,14 @@
"androidx.test.core",
"androidx.test.rules",
"androidx.test.espresso.core",
+ "flag-junit",
"mockito-target-minus-junit4",
+ "platform-test-annotations",
"truth-prebuilt",
"SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
+ "settingslib_flags_lib",
],
dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
new file mode 100644
index 0000000..c647cbb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.settingslib.media;
+
+import static com.android.settingslib.media.flags.Flags.FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.Context;
+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 androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InfoMediaManagerIntegTest {
+
+ private static final String FAKE_PACKAGE = "FAKE_PACKAGE";
+
+ private Context mContext;
+ private UiAutomation mUiAutomation;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
+ }
+
+ @After
+ public void tearDown() {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+ public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+ public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+ assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+ public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+ assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+ public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 368115b..215cc8e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -337,6 +337,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+ <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 712b737..0474849 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -201,6 +201,7 @@
"lottie",
"LowLightDreamLib",
"motion_tool_lib",
+ "IntentResolver-core",
],
manifest: "AndroidManifest.xml",
@@ -384,6 +385,7 @@
"motion_tool_lib",
"androidx.core_core-animation-testing-nodeps",
"androidx.compose.ui_ui",
+ "IntentResolver-core",
],
}
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index bba926d..0f1f168 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -89,7 +89,6 @@
tracyzhou@google.com
tsuji@google.com
twickham@google.com
-vadimt@google.com
victortulias@google.com
winsonc@google.com
wleshner@google.com
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1bd8355..969c148 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -56,6 +56,20 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ },
+ {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
@@ -148,5 +162,21 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Postsubmit"
+ }
+ ]
+ }
]
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 5ed450a..c26cd12 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -25,8 +25,8 @@
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
-import android.widget.TextView;
import android.view.View;
+import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
@@ -55,6 +55,10 @@
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
+ actionBar.setDisplayOptions(
+ ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
+ | ActionBar.DISPLAY_SHOW_TITLE
+ | ActionBar.DISPLAY_HOME_AS_UP);
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
new file mode 100644
index 0000000..c94fad7
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SolidColorShader.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.surfaceeffects.shaders
+
+import android.graphics.RuntimeShader
+
+/** Simply renders a solid color. */
+class SolidColorShader(color: Int) : RuntimeShader(SHADER) {
+ // language=AGSL
+ private companion object {
+ private const val SHADER =
+ """
+ layout(color) uniform vec4 in_color;
+ vec4 main(vec2 p) {
+ return in_color;
+ }
+ """
+ }
+
+ init {
+ setColorUniform("in_color", color)
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
new file mode 100644
index 0000000..df07856
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaders/SparkleShader.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.graphics.RuntimeShader
+import android.graphics.Shader
+import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
+
+/**
+ * Renders sparkles based on the luma matte.
+ *
+ * For example, you can pass in simplex noise as the luma matte and have a cloud looking sparkles.
+ *
+ * You may want to utilize this shader by: (Preferred) 1. Create a RuntimeShaderEffect and set the
+ * [RenderEffect] to the target [View].
+ * 2. Create a custom [View], set the shader to the [Paint] and use [Canvas.drawPaint] in [onDraw].
+ */
+class SparkleShader : RuntimeShader(SPARKLE_SHADER) {
+ // language=AGSL
+ companion object {
+ private const val UNIFORMS =
+ """
+ // Used it for RenderEffect. For example:
+ // myView.setRenderEffect(
+ // RenderEffect.createRuntimeShaderEffect(SparkleShader(), "in_src")
+ // )
+ uniform shader in_src;
+ uniform half in_time;
+ uniform half in_pixelate;
+ uniform shader in_lumaMatte;
+ layout(color) uniform vec4 in_color;
+ """
+ private const val MAIN_SHADER =
+ """vec4 main(vec2 p) {
+ half3 src = in_src.eval(p).rgb;
+ half luma = getLuminosity(in_lumaMatte.eval(p).rgb);
+ half sparkle = sparkles(p - mod(p, in_pixelate), in_time);
+ half3 mask = maskLuminosity(in_color.rgb * sparkle, luma);
+
+ return vec4(src * mask * in_color.a, in_color.a);
+ }
+ """
+ private const val SPARKLE_SHADER = UNIFORMS + ShaderUtilLibrary.SHADER_LIB + MAIN_SHADER
+
+ /** Highly recommended to use this value unless specified by design spec. */
+ const val DEFAULT_SPARKLE_PIXELATE_AMOUNT = 0.8f
+ }
+
+ init {
+ // Initializes the src and luma matte to be white.
+ setInputShader("in_src", SolidColorShader(Color.WHITE))
+ setLumaMatteColor(Color.WHITE)
+ }
+
+ /**
+ * Sets the time of the sparkle animation.
+ *
+ * This is used for animating sparkles. Note that this only makes the sparkles sparkle in place.
+ * In order to move the sparkles in x, y directions, move the luma matte input instead.
+ */
+ fun setTime(time: Float) {
+ setFloatUniform("in_time", time)
+ }
+
+ /**
+ * Sets pixelated amount of the sparkle.
+ *
+ * This value *must* be based on [resources.displayMetrics.density]. Otherwise, this will result
+ * in having different sparkle sizes on different screens.
+ *
+ * Expected to be used as follows:
+ * <pre>
+ * {@code
+ * val pixelDensity = context.resources.displayMetrics.density
+ * // Sparkles will be 0.8 of the pixel size.
+ * val sparkleShader = SparkleShader().apply { setPixelateAmount(pixelDensity * 0.8f) }
+ * }
+ * </pre>
+ */
+ fun setPixelateAmount(pixelateAmount: Float) {
+ setFloatUniform("in_pixelate", pixelateAmount)
+ }
+
+ /**
+ * Sets the luma matte for the sparkles. The luminosity determines the sparkle's visibility.
+ * Useful for setting a complex mask (e.g. simplex noise, texture, etc.)
+ */
+ fun setLumaMatte(lumaMatte: Shader) {
+ setInputShader("in_lumaMatte", lumaMatte)
+ }
+
+ /** Sets the luma matte for the sparkles. Useful for setting a solid color. */
+ fun setLumaMatteColor(color: Int) {
+ setInputShader("in_lumaMatte", SolidColorShader(color))
+ }
+
+ /** Sets the color of the sparkles. Expect to have the alpha value encoded. */
+ fun setColor(color: Int) {
+ setColorUniform("in_color", color)
+ }
+}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index 5413f90..24064b1 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -17,12 +17,10 @@
package com.android.systemui.scene.ui.composable
import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneContainerNames
import dagger.Module
import dagger.multibindings.Multibinds
-import javax.inject.Named
@Module
interface SceneModule {
- @Multibinds @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) fun scenes(): Set<Scene>
+ @Multibinds fun scenes(): Set<Scene>
}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
index d364374..3e9b397 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -16,35 +16,29 @@
package com.android.systemui.scene.ui.composable
+import android.app.AlertDialog
import android.content.Context
import com.android.systemui.bouncer.ui.composable.BouncerScene
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.composable.BouncerSceneDialogFactory
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.composable.LockscreenScene
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.qs.ui.composable.QuickSettingsScene
-import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Scene
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.shade.ui.composable.ShadeScene
-import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import dagger.Module
import dagger.Provides
-import javax.inject.Named
-import kotlinx.coroutines.CoroutineScope
@Module
object SceneModule {
@Provides
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
fun scenes(
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) bouncer: BouncerScene,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) gone: GoneScene,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) lockScreen: LockscreenScene,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) qs: QuickSettingsScene,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT) shade: ShadeScene,
+ bouncer: BouncerScene,
+ gone: GoneScene,
+ lockScreen: LockscreenScene,
+ qs: QuickSettingsScene,
+ shade: ShadeScene,
): Set<Scene> {
return setOf(
bouncer,
@@ -57,70 +51,11 @@
@Provides
@SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun bouncerScene(
- @Application context: Context,
- viewModelFactory: BouncerViewModel.Factory,
- ): BouncerScene {
- return BouncerScene(
- viewModel =
- viewModelFactory.create(
- containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
- ),
- dialogFactory = { SystemUIDialog(context) },
- )
- }
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun goneScene(): GoneScene {
- return GoneScene()
- }
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun lockscreenScene(
- @Application applicationScope: CoroutineScope,
- viewModelFactory: LockscreenSceneViewModel.Factory,
- ): LockscreenScene {
- return LockscreenScene(
- applicationScope = applicationScope,
- viewModel =
- viewModelFactory.create(
- containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
- ),
- )
- }
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun quickSettingsScene(
- viewModelFactory: QuickSettingsSceneViewModel.Factory,
- ): QuickSettingsScene {
- return QuickSettingsScene(
- viewModel =
- viewModelFactory.create(
- containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
- ),
- )
- }
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun shadeScene(
- @Application applicationScope: CoroutineScope,
- viewModelFactory: ShadeSceneViewModel.Factory,
- ): ShadeScene {
- return ShadeScene(
- applicationScope = applicationScope,
- viewModel =
- viewModelFactory.create(
- containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
- ),
- )
+ fun bouncerSceneDialogFactory(@Application context: Context): BouncerSceneDialogFactory {
+ return object : BouncerSceneDialogFactory {
+ override fun invoke(): AlertDialog {
+ return SystemUIDialog(context)
+ }
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d83596e..6d9497d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -52,24 +52,27 @@
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.dagger.SysUISingleton
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 javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
-class BouncerScene(
+@SysUISingleton
+class BouncerScene
+@Inject
+constructor(
private val viewModel: BouncerViewModel,
- private val dialogFactory: () -> AlertDialog,
+ private val dialogFactory: BouncerSceneDialogFactory,
) : ComposableScene {
override val key = SceneKey.Bouncer
- override fun destinationScenes(
- containerName: String,
- ): StateFlow<Map<UserAction, SceneModel>> =
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow<Map<UserAction, SceneModel>>(
mapOf(
UserAction.Back to SceneModel(SceneKey.Lockscreen),
@@ -79,7 +82,6 @@
@Composable
override fun Content(
- containerName: String,
modifier: Modifier,
) = BouncerScene(viewModel, dialogFactory, modifier)
}
@@ -87,7 +89,7 @@
@Composable
private fun BouncerScene(
viewModel: BouncerViewModel,
- dialogFactory: () -> AlertDialog,
+ dialogFactory: BouncerSceneDialogFactory,
modifier: Modifier = Modifier,
) {
val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
@@ -175,3 +177,7 @@
}
}
}
+
+interface BouncerSceneDialogFactory {
+ operator fun invoke(): AlertDialog
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 1065267..ab7bc26 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -31,6 +31,7 @@
import androidx.compose.ui.unit.dp
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.scene.shared.model.Direction
@@ -38,6 +39,7 @@
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 javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -45,15 +47,16 @@
import kotlinx.coroutines.flow.stateIn
/** The lock screen scene shows when the device is locked. */
-class LockscreenScene(
+@SysUISingleton
+class LockscreenScene
+@Inject
+constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: LockscreenSceneViewModel,
) : ComposableScene {
override val key = SceneKey.Lockscreen
- override fun destinationScenes(
- containerName: String,
- ): StateFlow<Map<UserAction, SceneModel>> =
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
viewModel.upDestinationSceneKey
.map { pageKey -> destinationScenes(up = pageKey) }
.stateIn(
@@ -64,7 +67,6 @@
@Composable
override fun Content(
- containerName: String,
modifier: Modifier,
) {
LockscreenScene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index f88fc21..d84e676 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -20,7 +20,6 @@
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -28,9 +27,9 @@
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -39,6 +38,7 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
@@ -134,10 +134,11 @@
)
}
- LazyColumn(
- Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
- contentPadding =
- PaddingValues(
+ Column(
+ Modifier.fillMaxWidth()
+ .sysuiResTag("scroll_view")
+ .verticalScroll(rememberScrollState())
+ .padding(
top = 16.dp,
bottom = PeopleSpacePadding,
start = 8.dp,
@@ -151,7 +152,7 @@
if (recentTiles.isNotEmpty()) {
if (hasPriorityConversations) {
- item { Spacer(Modifier.height(35.dp)) }
+ Spacer(Modifier.height(35.dp))
}
ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
@@ -160,33 +161,30 @@
}
}
-private fun LazyListScope.ConversationList(
+@Composable
+private fun ConversationList(
@StringRes headerTextResource: Int,
tiles: List<PeopleTileViewModel>,
onTileClicked: (PeopleTileViewModel) -> Unit
) {
- item {
- Text(
- stringResource(headerTextResource),
- Modifier.padding(start = 16.dp),
- style = MaterialTheme.typography.labelLarge,
- color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant,
- )
+ Text(
+ stringResource(headerTextResource),
+ Modifier.padding(start = 16.dp),
+ style = MaterialTheme.typography.labelLarge,
+ color = LocalAndroidColorScheme.current.deprecated.colorAccentPrimaryVariant,
+ )
- Spacer(Modifier.height(10.dp))
- }
+ Spacer(Modifier.height(10.dp))
tiles.forEachIndexed { index, tile ->
if (index > 0) {
- item {
- Divider(
- color = LocalAndroidColorScheme.current.deprecated.colorBackground,
- thickness = 2.dp,
- )
- }
+ Divider(
+ color = LocalAndroidColorScheme.current.deprecated.colorBackground,
+ thickness = 2.dp,
+ )
}
- item(tile.key.toString()) {
+ key(tile.key.toString()) {
Tile(
tile,
onTileClicked,
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 30b80ca..130395a 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
@@ -27,25 +27,28 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
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 javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
-class QuickSettingsScene(
+@SysUISingleton
+class QuickSettingsScene
+@Inject
+constructor(
private val viewModel: QuickSettingsSceneViewModel,
) : ComposableScene {
override val key = SceneKey.QuickSettings
- override fun destinationScenes(
- containerName: String,
- ): StateFlow<Map<UserAction, SceneModel>> =
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow<Map<UserAction, SceneModel>>(
mapOf(
UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
@@ -55,7 +58,6 @@
@Composable
override fun Content(
- containerName: String,
modifier: Modifier,
) {
QuickSettingsScene(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
index 6f3363e..a213666 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
@@ -22,5 +22,5 @@
/** Compose-capable extension of [Scene]. */
interface ComposableScene : Scene {
- @Composable fun Content(containerName: String, modifier: Modifier)
+ @Composable fun Content(modifier: Modifier)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 0a4da1d..0070552 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -23,10 +23,12 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import com.android.systemui.dagger.SysUISingleton
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 javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -35,12 +37,11 @@
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
* content from the scene framework.
*/
-class GoneScene : ComposableScene {
+@SysUISingleton
+class GoneScene @Inject constructor() : ComposableScene {
override val key = SceneKey.Gone
- override fun destinationScenes(
- containerName: String,
- ): StateFlow<Map<UserAction, SceneModel>> =
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow<Map<UserAction, SceneModel>>(
mapOf(
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
@@ -50,7 +51,6 @@
@Composable
override fun Content(
- containerName: String,
modifier: Modifier,
) {
/*
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 3298664..49e2bf9 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
@@ -75,7 +75,6 @@
if (key == currentSceneKey) {
Scene(
scene = composableScene,
- containerName = viewModel.containerName,
onSceneChanged = viewModel::setCurrentScene,
modifier = Modifier.fillMaxSize(),
)
@@ -88,12 +87,10 @@
@Composable
private fun Scene(
scene: ComposableScene,
- containerName: String,
onSceneChanged: (SceneModel) -> Unit,
modifier: Modifier = Modifier,
) {
- val destinationScenes: Map<UserAction, SceneModel> by
- scene.destinationScenes(containerName).collectAsState()
+ val destinationScenes: Map<UserAction, SceneModel> by scene.destinationScenes().collectAsState()
val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
@@ -107,7 +104,6 @@
modifier = Modifier.align(Alignment.Center),
) {
scene.Content(
- containerName = containerName,
modifier = Modifier,
)
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 27358f5..b73e0b2 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
@@ -26,6 +26,7 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+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
@@ -35,6 +36,7 @@
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -42,15 +44,16 @@
import kotlinx.coroutines.flow.stateIn
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
-class ShadeScene(
+@SysUISingleton
+class ShadeScene
+@Inject
+constructor(
@Application private val applicationScope: CoroutineScope,
private val viewModel: ShadeSceneViewModel,
) : ComposableScene {
override val key = SceneKey.Shade
- override fun destinationScenes(
- containerName: String,
- ): StateFlow<Map<UserAction, SceneModel>> =
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
viewModel.upDestinationSceneKey
.map { sceneKey -> destinationScenes(up = sceneKey) }
.stateIn(
@@ -61,7 +64,6 @@
@Composable
override fun Content(
- containerName: String,
modifier: Modifier,
) = ShadeScene(viewModel, modifier)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index d43276c..46f5971 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -190,6 +190,10 @@
/** Flag denoting transit clock are enabled in wallpaper picker. */
const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
+ /** Flag denoting whether preview loading animation is enabled. */
+ const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION =
+ "wallpaper_picker_preview_animation"
+
object Columns {
/** String. Unique ID for the flag. */
const val NAME = "name"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index c7d2d81..29e14c5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -16,202 +16,10 @@
** limitations under the License.
*/
-->
-
-<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/res-auto"
- android:id="@+id/keyguard_pin_view"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_horizontal|bottom"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:orientation="vertical"
- androidprv:layout_maxWidth="@dimen/keyguard_security_width">
-<include layout="@layout/keyguard_bouncer_message_area"/>
+ android:layout_height="match_parent">
-<com.android.systemui.bouncer.ui.BouncerMessageView
- android:id="@+id/bouncer_message_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" />
+ <include layout="@layout/keyguard_pin_view_portrait" />
-<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/pin_container"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_marginBottom="8dp"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:layout_weight="1"
- android:layoutDirection="ltr"
- android:orientation="vertical">
-
- <!-- Set this to be just above key1. It would be better to introduce a barrier above
- key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
- drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
- case, the Flow should ensure that key1/2/3 all have the same top, so this should be
- fine. -->
- <com.android.keyguard.AlphaOptimizedRelativeLayout
- android:id="@+id/row0"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
- androidprv:layout_constraintTop_toTopOf="parent"
- androidprv:layout_constraintEnd_toEndOf="parent"
- androidprv:layout_constraintStart_toStartOf="parent"
- androidprv:layout_constraintBottom_toTopOf="@id/key1"
- androidprv:layout_constraintVertical_bias="0.5">
-
- <com.android.keyguard.PasswordTextView
- android:id="@+id/pinEntry"
- style="@style/Widget.TextView.Password"
- android:layout_width="@dimen/keyguard_security_width"
- android:layout_height="@dimen/keyguard_password_height"
- android:layout_centerHorizontal="true"
- android:layout_marginRight="72dp"
- android:contentDescription="@string/keyguard_accessibility_pin_area"
- androidprv:scaledTextSize="@integer/scaled_password_text_size" />
- </com.android.keyguard.AlphaOptimizedRelativeLayout>
-
- <!-- Guideline used to place the top row of keys relative to the screen height. This will be
- updated in KeyguardPINView to reduce the height of the PIN pad. -->
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/pin_pad_top_guideline"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- androidprv:layout_constraintGuide_percent="0"
- android:orientation="horizontal" />
-
- <com.android.keyguard.KeyguardPinFlowView
- android:id="@+id/flow1"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:orientation="horizontal"
- android:clipChildren="false"
- android:clipToPadding="false"
-
- androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
-
- androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
-
- androidprv:flow_horizontalStyle="packed"
- androidprv:flow_maxElementsWrap="3"
-
- androidprv:flow_verticalBias="1.0"
- androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
- androidprv:flow_verticalStyle="packed"
-
- androidprv:flow_wrapMode="aligned"
- androidprv:layout_constraintBottom_toBottomOf="parent"
- androidprv:layout_constraintEnd_toEndOf="parent"
- androidprv:layout_constraintStart_toStartOf="parent"
- androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key1"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key2"
- androidprv:digit="1"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key2"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key3"
- androidprv:digit="2"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key3"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key4"
- androidprv:digit="3"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key4"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key5"
- androidprv:digit="4"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key5"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key6"
- androidprv:digit="5"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key6"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key7"
- androidprv:digit="6"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key7"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key8"
- androidprv:digit="7"
- androidprv:textView="@+id/pinEntry" />
-
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key8"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key9"
- androidprv:digit="8"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key9"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/delete_button"
- androidprv:digit="9"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadButton
- android:id="@+id/delete_button"
- style="@style/NumPadKey.Delete"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key0"
- android:contentDescription="@string/keyboardview_keycode_delete" />
-
- <com.android.keyguard.NumPadKey
- android:id="@+id/key0"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:accessibilityTraversalBefore="@id/key_enter"
- androidprv:digit="0"
- androidprv:textView="@+id/pinEntry" />
-
- <com.android.keyguard.NumPadButton
- android:id="@+id/key_enter"
- style="@style/NumPadKey.Enter"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:contentDescription="@string/keyboardview_keycode_enter" />
-</androidx.constraintlayout.widget.ConstraintLayout>
-
- <include layout="@layout/keyguard_eca"
- android:id="@+id/keyguard_selector_fade_container"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_gravity="bottom|center_horizontal"
- android:layout_marginTop="@dimen/keyguard_eca_top_margin"
- android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
- android:gravity="center_horizontal"/>
-
-</com.android.keyguard.KeyguardPINView>
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml
new file mode 100644
index 0000000..f3cd9e4
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_portrait.xml
@@ -0,0 +1,218 @@
+<?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.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_pin_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ androidprv:layout_maxWidth="@dimen/keyguard_security_width">
+
+ <include layout="@layout/keyguard_bouncer_message_area"/>
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/pin_container"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_weight="1"
+ android:layoutDirection="ltr"
+ android:orientation="vertical">
+
+ <!-- Set this to be just above key1. It would be better to introduce a barrier above
+ key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+ drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+ case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+ fine. -->
+ <com.android.keyguard.AlphaOptimizedRelativeLayout
+ android:id="@+id/row0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@id/key1"
+ androidprv:layout_constraintVertical_bias="0.5">
+
+ <com.android.keyguard.PasswordTextView
+ android:id="@+id/pinEntry"
+ style="@style/Widget.TextView.Password"
+ android:layout_width="@dimen/keyguard_security_width"
+ android:layout_height="@dimen/keyguard_password_height"
+ android:layout_centerHorizontal="true"
+ android:layout_marginRight="72dp"
+ android:contentDescription="@string/keyguard_accessibility_pin_area"
+ androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+ </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+ <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+ updated in KeyguardPINView to reduce the height of the PIN pad. -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pin_pad_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintGuide_percent="0"
+ android:orientation="horizontal" />
+
+ <com.android.keyguard.KeyguardPinFlowView
+ android:id="@+id/flow1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+
+ androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+ androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+ androidprv:flow_horizontalStyle="packed"
+ androidprv:flow_maxElementsWrap="3"
+
+ androidprv:flow_verticalBias="1.0"
+ androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+ androidprv:flow_verticalStyle="packed"
+
+ androidprv:flow_wrapMode="aligned"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key1"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key2"
+ androidprv:digit="1"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key2"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key3"
+ androidprv:digit="2"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key3"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key4"
+ androidprv:digit="3"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key4"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key5"
+ androidprv:digit="4"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key5"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key6"
+ androidprv:digit="5"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key6"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key7"
+ androidprv:digit="6"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key7"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key8"
+ androidprv:digit="7"
+ androidprv:textView="@+id/pinEntry" />
+
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key8"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key9"
+ androidprv:digit="8"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key9"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/delete_button"
+ androidprv:digit="9"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/delete_button"
+ style="@style/NumPadKey.Delete"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key0"
+ android:contentDescription="@string/keyboardview_keycode_delete" />
+
+ <com.android.keyguard.NumPadKey
+ android:id="@+id/key0"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:accessibilityTraversalBefore="@id/key_enter"
+ androidprv:digit="0"
+ androidprv:textView="@+id/pinEntry" />
+
+ <com.android.keyguard.NumPadButton
+ android:id="@+id/key_enter"
+ style="@style/NumPadKey.Enter"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/keyboardview_keycode_enter" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <include layout="@layout/keyguard_eca"
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="bottom|center_horizontal"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:gravity="center_horizontal"/>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index e474938..5404cfa 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.internal.widget.ResolverDrawerLayout
+<com.android.intentresolver.widget.ResolverDrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
@@ -84,7 +84,7 @@
android:id="@*android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <com.android.internal.app.ResolverViewPager
+ <com.android.intentresolver.ResolverViewPager
android:id="@*android:id/profile_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
@@ -92,4 +92,4 @@
</LinearLayout>
</TabHost>
-</com.android.internal.widget.ResolverDrawerLayout>
+</com.android.intentresolver.widget.ResolverDrawerLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de8287e..68fcdea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1196,7 +1196,6 @@
<dimen name="magnification_window_drag_corner_stroke">3dp</dimen>
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
- <dimen name="magnification_max_frame_size">300dp</dimen>
<!-- Magnification settings panel -->
<dimen name="magnification_setting_view_margin">24dp</dimen>
<dimen name="magnification_setting_text_size">18sp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4d3a742..2c224f62 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -423,6 +423,7 @@
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
+ mLogBuffer.log(TAG, LogLevel.INFO, "refresh");
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 61addab..8a0c9ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1198,6 +1198,8 @@
});
mPopup.show();
});
+
+ mUserSwitcherViewGroup.setAlpha(0f);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index bc24249..3b09910f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,6 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneContainerNames;
import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -476,7 +475,7 @@
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
// When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
- mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
+ mSceneInteractor.get().getTransitions(),
sceneTransitionModel -> {
if (sceneTransitionModel != null
&& sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index fbacd68..bc5b1ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -117,15 +117,19 @@
KeyguardSecurityCallback keyguardSecurityCallback,
@Nullable OnViewInflatedCallback onViewInflatedListener) {
int layoutId = getLayoutIdFor(securityMode);
- if (layoutId != 0) {
- if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId);
+ int viewID = getKeyguardInputViewId(securityMode);
+ if (layoutId != 0 && viewID != 0) {
+ if (DEBUG) {
+ Log.v(TAG, "inflating on bg thread id = "
+ + layoutId + " . viewID = " + viewID);
+ }
mAsyncLayoutInflater.inflate(layoutId, mView,
(view, resId, parent) -> {
mView.addView(view);
KeyguardInputViewController<KeyguardInputView> childController =
mKeyguardSecurityViewControllerFactory.create(
- (KeyguardInputView) view, securityMode,
- keyguardSecurityCallback);
+ (KeyguardInputView) view.findViewById(viewID),
+ securityMode, keyguardSecurityCallback);
childController.init();
mChildren.add(childController);
if (onViewInflatedListener != null) {
@@ -147,6 +151,19 @@
}
}
+ private int getKeyguardInputViewId(SecurityMode securityMode) {
+ //Keyguard Input View is not the root view of the layout, use these IDs for lookup.
+ switch (securityMode) {
+ case Pattern: return R.id.keyguard_pattern_view;
+ case PIN: return R.id.keyguard_pin_view;
+ case Password: return R.id.keyguard_password_view;
+ case SimPin: return R.id.keyguard_sim_pin_view;
+ case SimPuk: return R.id.keyguard_sim_puk_view;
+ default:
+ return 0;
+ }
+ }
+
/** Makes the supplied child visible if it is contained win this view, */
public void show(KeyguardInputViewController<KeyguardInputView> childController) {
int index = childController.getIndexIn(mView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a04d13b..8e92941 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.Animator;
import android.animation.ValueAnimator;
@@ -51,6 +52,10 @@
import com.android.systemui.R;
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.shared.model.ScreenModel;
+import com.android.systemui.keyguard.shared.model.ScreenState;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -62,6 +67,9 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
+import kotlin.coroutines.CoroutineContext;
+import kotlin.coroutines.EmptyCoroutineContext;
+
import java.io.PrintWriter;
import javax.inject.Inject;
@@ -91,6 +99,7 @@
private final FeatureFlags mFeatureFlags;
private final InteractionJankMonitor mInteractionJankMonitor;
private final Rect mClipBounds = new Rect();
+ private final KeyguardInteractor mKeyguardInteractor;
private Boolean mStatusViewCentered = true;
@@ -122,6 +131,7 @@
KeyguardLogger logger,
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
+ KeyguardInteractor keyguardInteractor,
DumpManager dumpManager) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
@@ -134,12 +144,34 @@
mInteractionJankMonitor = interactionJankMonitor;
mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
+ mKeyguardInteractor = keyguardInteractor;
}
@Override
public void onInit() {
mKeyguardClockSwitchController.init();
mDumpManager.registerDumpable(this);
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ startCoroutines(EmptyCoroutineContext.INSTANCE);
+ }
+ }
+
+ void startCoroutines(CoroutineContext context) {
+ collectFlow(mView, mKeyguardInteractor.getDozeTimeTick(),
+ (Long millis) -> {
+ dozeTimeTick();
+ }, context);
+
+ collectFlow(mView, mKeyguardInteractor.getScreenModel(),
+ (ScreenModel model) -> {
+ if (model.getState() == ScreenState.SCREEN_TURNING_ON) {
+ dozeTimeTick();
+ }
+ }, context);
+ }
+
+ public KeyguardStatusView getView() {
+ return mView;
}
@Override
@@ -308,6 +340,7 @@
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onTimeChanged() {
+ Slog.v(TAG, "onTimeChanged");
refreshTime();
}
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index f00615b..7c377d2 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -19,9 +19,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.FloatProperty;
import android.util.Log;
@@ -34,6 +31,11 @@
import android.view.View;
import android.view.ViewConfiguration;
+import androidx.annotation.NonNull;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.ObjectAnimator;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -616,7 +618,7 @@
public boolean mCancelled;
@Override
- public void onAnimationEnd(Animator animation) {
+ public void onAnimationEnd(@NonNull Animator animation) {
if (!mCancelled) {
mCallback.setUserExpandedChild(scaledView, expand);
if (!mExpanding) {
@@ -633,7 +635,7 @@
}
@Override
- public void onAnimationCancel(Animator animation) {
+ public void onAnimationCancel(@NonNull Animator animation) {
mCancelled = true;
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 6c8f8f3..4a31f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -124,7 +124,7 @@
private float mScale;
/**
- * MagnificationFrame represents the bound of {@link #mMirrorSurface} and is constrained
+ * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained
* by the {@link #mMagnificationFrameBoundary}.
* We use MagnificationFrame to calculate the position of {@link #mMirrorView}.
* We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and
@@ -225,6 +225,8 @@
private boolean mAllowDiagonalScrolling = false;
private boolean mEditSizeEnable = false;
private boolean mSettingsPanelVisibility = false;
+ @VisibleForTesting
+ WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
@Nullable
private final MirrorWindowControl mMirrorWindowControl;
@@ -249,6 +251,7 @@
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
mConfiguration = new Configuration(context.getResources().getConfiguration());
+ mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
final Display display = mContext.getDisplay();
mDisplayId = mContext.getDisplayId();
@@ -272,8 +275,8 @@
com.android.internal.R.integer.config_shortAnimTime);
updateDimensions();
- final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
- setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(),
+ final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+ setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
mWindowBounds.width() / 2, mWindowBounds.height() / 2);
computeBounceAnimationScale();
@@ -381,12 +384,16 @@
if (!mMagnificationSizeScaleOptions.contains(index)) {
return;
}
- final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
- final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
- int size = (int) (initSize * scale);
+ int size = getMagnificationWindowSizeFromIndex(index);
setWindowSize(size, size);
}
+ int getMagnificationWindowSizeFromIndex(@MagnificationSize int index) {
+ final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
+ int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+ return (int) (initSize * scale) - (int) (initSize * scale) % 2;
+ }
+
void setEditMagnifierSizeMode(boolean enable) {
mEditSizeEnable = enable;
applyResourcesValues();
@@ -395,6 +402,12 @@
updateDimensions();
applyTapExcludeRegion();
}
+
+ if (!enable) {
+ // Keep the magnifier size when exiting edit mode
+ mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+ new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
+ }
}
void setDiagonalScrolling(boolean enable) {
@@ -519,12 +532,12 @@
return false;
}
mWindowBounds.set(currentWindowBounds);
- final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
+ final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
- setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), (int) newCenterX,
- (int) newCenterY);
+ setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
+ (int) newCenterX, (int) newCenterY);
calculateMagnificationFrameBoundary();
return true;
}
@@ -738,6 +751,8 @@
}
private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
+ mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+
// Sets the initial frame area for the mirror and place it to the given center on the
// display.
final int initX = centerX - width / 2;
@@ -745,12 +760,18 @@
mMagnificationFrame.set(initX, initY, initX + width, initY + height);
}
- private Size getDefaultWindowSizeWithWindowBounds(Rect windowBounds) {
- int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2;
- initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size),
- initSize);
- initSize += 2 * mMirrorSurfaceMargin;
- return new Size(initSize, initSize);
+ private Size restoreMagnificationWindowFrameSizeIfPossible() {
+ if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) {
+ return getDefaultMagnificationWindowFrameSize();
+ }
+
+ return mWindowMagnificationSizePrefs.getSizeForCurrentDensity();
+ }
+
+ private Size getDefaultMagnificationWindowFrameSize() {
+ final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM)
+ - 2 * mMirrorSurfaceMargin;
+ return new Size(defaultSize, defaultSize);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
new file mode 100644
index 0000000..4d7ad264
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
@@ -0,0 +1,71 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Size;
+
+/**
+ * Class to handle SharedPreference for window magnification size.
+ */
+public final class WindowMagnificationSizePrefs {
+
+ private static final String WINDOW_MAGNIFICATION_PREFERENCES =
+ "window_magnification_preferences";
+ Context mContext;
+ SharedPreferences mWindowMagnificationSizePreferences;
+
+ public WindowMagnificationSizePrefs(Context context) {
+ mContext = context;
+ mWindowMagnificationSizePreferences = mContext
+ .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Uses smallest screen width DP as the key for preference.
+ */
+ private String getKey() {
+ return String.valueOf(
+ mContext.getResources().getConfiguration().smallestScreenWidthDp);
+ }
+
+ /**
+ * Saves the window frame size for current screen density.
+ */
+ public void saveSizeForCurrentDensity(Size size) {
+ mWindowMagnificationSizePreferences.edit()
+ .putString(getKey(), size.toString()).apply();
+ }
+
+ /**
+ * Check if there is a preference saved for current screen density.
+ *
+ * @return true if there is a preference saved for current screen density, false if it is unset.
+ */
+ public boolean isPreferenceSavedForCurrentDensity() {
+ return mWindowMagnificationSizePreferences.contains(getKey());
+ }
+
+ /**
+ * Gets the size preference for current screen density.
+ */
+ public Size getSizeForCurrentDensity() {
+ return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index e60d4e1..0c7d56f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -100,6 +100,9 @@
)
} else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
animateIconOnce(R.drawable.face_dialog_dark_to_error)
+ iconView.contentDescription = context.getString(
+ R.string.keyguard_face_failed
+ )
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index a1b15f44..d82f458 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -122,9 +122,7 @@
if (shouldAnimateIconViewForTransition(lastState, newState)) {
iconView.playAnimation()
}
- if (isSideFps) {
- LottieColorUtils.applyDynamicColors(context, iconView)
- }
+ LottieColorUtils.applyDynamicColors(context, iconView)
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ebff0b0..39a45f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -26,6 +26,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -50,6 +51,7 @@
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.util.Log;
+import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -234,6 +236,8 @@
public static final VibrationEffect EFFECT_CLICK =
VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS;
+
private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
@@ -926,12 +930,24 @@
@VisibleForTesting
public void playStartHaptic() {
if (mAccessibilityManager.isTouchExplorationEnabled()) {
- mVibrator.vibrate(
- Process.myUid(),
- mContext.getOpPackageName(),
- EFFECT_CLICK,
- "udfps-onStart-click",
- UDFPS_VIBRATION_ATTRIBUTES);
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ mVibrator.performHapticFeedback(
+ mOverlay.getOverlayView(),
+ HapticFeedbackConstants.CONTEXT_CLICK
+ );
+ } else {
+ Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+ + "vibration. Either the controller overlay is null or has no view");
+ }
+ } else {
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ EFFECT_CLICK,
+ "udfps-onStart-click",
+ UDFPS_VIBRATION_ATTRIBUTES);
+ }
}
}
@@ -1024,12 +1040,24 @@
mKeyguardViewManager.showPrimaryBouncer(true);
// play the same haptic as the LockIconViewController longpress
- mVibrator.vibrate(
- Process.myUid(),
- mContext.getOpPackageName(),
- UdfpsController.EFFECT_CLICK,
- "aod-lock-icon-longpress",
- LOCK_ICON_VIBRATION_ATTRIBUTES);
+ if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+ if (mOverlay != null && mOverlay.getOverlayView() != null) {
+ mVibrator.performHapticFeedback(
+ mOverlay.getOverlayView(),
+ UdfpsController.LONG_PRESS
+ );
+ } else {
+ Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+ + "vibration. Either the controller overlay is null or has no view");
+ }
+ } else {
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "aod-lock-icon-longpress",
+ LOCK_ICON_VIBRATION_ATTRIBUTES);
+ }
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 9bbf1ef..b68b921 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -26,6 +26,7 @@
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.View
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
import android.widget.TextView
@@ -335,6 +336,13 @@
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
+ // Disable background view for cancelling authentication once authenticated,
+ // and remove from talkback
+ if (authState.isAuthenticated) {
+ backgroundView.setOnClickListener(null)
+ backgroundView.importantForAccessibility =
+ IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
if (authState.isAuthenticatedAndConfirmed) {
view.announceForAccessibility(
view.resources.getString(R.string.biometric_dialog_authenticated)
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 8e14237..d8cf398 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
@@ -24,6 +24,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -31,9 +32,7 @@
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.util.kotlin.pairwise
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,8 +43,9 @@
import kotlinx.coroutines.launch
/** Encapsulates business logic and application state accessing use-cases. */
+@SysUISingleton
class BouncerInteractor
-@AssistedInject
+@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val applicationContext: Context,
@@ -53,7 +53,6 @@
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
featureFlags: FeatureFlags,
- @Assisted private val containerName: String,
) {
/** The user-facing message to show in the bouncer. */
@@ -118,23 +117,19 @@
/**
* Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
*
- * @param containerName The name of the scene container to show the bouncer in.
* @param message An optional message to show to the user in the bouncer.
*/
fun showOrUnlockDevice(
- containerName: String,
message: String? = null,
) {
applicationScope.launch {
if (authenticationInteractor.isAuthenticationRequired()) {
repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
sceneInteractor.setCurrentScene(
- containerName = containerName,
scene = SceneModel(SceneKey.Bouncer),
)
} else {
sceneInteractor.setCurrentScene(
- containerName = containerName,
scene = SceneModel(SceneKey.Gone),
)
}
@@ -180,7 +175,6 @@
if (isAuthenticated) {
sceneInteractor.setCurrentScene(
- containerName = containerName,
scene = SceneModel(SceneKey.Gone),
)
} else {
@@ -228,11 +222,4 @@
else -> ""
}
}
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): BouncerInteractor
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index 34e934b..d9ec5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -120,6 +120,8 @@
viewModel.isShowing.collect { isShowing ->
view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
if (isShowing) {
+ // Reset security container because these views are not reinflated.
+ securityContainerController.reset()
securityContainerController.reinflateViewFlipper {
// Reset Security Container entirely.
securityContainerController.onBouncerVisibilityChanged(
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 a4ef5ce..68e1a29 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
@@ -22,13 +22,12 @@
import com.android.systemui.R
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.kotlin.pairwise
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlin.math.ceil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,17 +44,15 @@
import kotlinx.coroutines.launch
/** Holds UI state and handles user input on bouncer UIs. */
+@SysUISingleton
class BouncerViewModel
-@AssistedInject
+@Inject
constructor(
@Application private val applicationContext: Context,
@Application private val applicationScope: CoroutineScope,
- interactorFactory: BouncerInteractor.Factory,
+ private val interactor: BouncerInteractor,
featureFlags: FeatureFlags,
- @Assisted containerName: String,
) {
- private val interactor: BouncerInteractor = interactorFactory.create(containerName)
-
private val isInputEnabled: StateFlow<Boolean> =
interactor.isThrottled
.map { !it }
@@ -222,11 +219,4 @@
*/
val isUpdateAnimated: Boolean,
)
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): BouncerViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index b387e4a..4c9dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -40,8 +40,6 @@
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -50,7 +48,6 @@
* Activity for rearranging and removing controls for a given structure
*/
open class ControlsEditingActivity @Inject constructor(
- featureFlags: FeatureFlags,
@Main private val mainExecutor: Executor,
private val controller: ControlsControllerImpl,
private val userTracker: UserTracker,
@@ -76,8 +73,6 @@
private var isFromFavoriting: Boolean = false
- private val isNewFlowEnabled: Boolean =
- featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
@@ -176,7 +171,7 @@
private fun bindButtons() {
addControls = requireViewById<Button>(R.id.addControls).apply {
isEnabled = true
- visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+ visibility = View.VISIBLE
setOnClickListener {
if (saveButton.isEnabled) {
// The user has made changes
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 59fa7f5..23721c9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -33,7 +33,6 @@
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
-import android.widget.Toast
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
@@ -41,24 +40,19 @@
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
-import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.ui.ControlsActivity
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import java.text.Collator
import java.util.concurrent.Executor
import javax.inject.Inject
open class ControlsFavoritingActivity @Inject constructor(
- featureFlags: FeatureFlags,
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
- private val listingController: ControlsListingController,
private val userTracker: UserTracker,
) : ComponentActivity() {
@@ -92,7 +86,6 @@
private lateinit var pageIndicator: ManagementPageIndicator
private var mTooltipManager: TooltipManager? = null
private lateinit var doneButton: View
- private lateinit var otherAppsButton: View
private lateinit var rearrangeButton: Button
private var listOfStructures = emptyList<StructureContainer>()
@@ -104,8 +97,6 @@
get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
private val fromEditing: Boolean
get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
- private val isNewFlowEnabled: Boolean =
- featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
private val startingUser = controller.currentUserId
@@ -124,20 +115,6 @@
onBackPressed()
}
- private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-
- override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
- if (serviceInfos.size > 1) {
- val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE
- if (otherAppsButton.visibility != newVisibility) {
- otherAppsButton.post {
- otherAppsButton.visibility = newVisibility
- }
- }
- }
- }
- }
-
override fun onBackPressed() {
if (fromEditing) {
animateExitAndFinish()
@@ -342,7 +319,7 @@
getString(R.string.controls_favorite_rearrange_button)
}
isEnabled = false
- visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+ visibility = View.VISIBLE
setOnClickListener {
if (component == null) return@setOnClickListener
saveFavorites()
@@ -361,24 +338,6 @@
)
}
}
- otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
- setOnClickListener {
- if (doneButton.isEnabled) {
- // The user has made changes
- Toast.makeText(
- applicationContext,
- R.string.controls_favorite_toast_no_changes,
- Toast.LENGTH_SHORT
- ).show()
- }
- startActivity(
- Intent(context, ControlsProviderSelectorActivity::class.java),
- ActivityOptions
- .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
- )
- animateExitAndFinish()
- }
- }
doneButton = requireViewById<Button>(R.id.done).apply {
isEnabled = false
@@ -415,7 +374,6 @@
override fun onStart() {
super.onStart()
- listingController.addCallback(listingCallback)
userTracker.addCallback(userTrackerCallback, executor)
if (DEBUG) {
@@ -440,7 +398,6 @@
override fun onStop() {
super.onStop()
- listingController.removeCallback(listingCallback)
userTracker.removeCallback(userTrackerCallback)
if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 1eba667..83bec66 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -124,8 +124,7 @@
}
private fun updateServices(newServices: List<ControlsServiceInfo>) {
- if (featureFlags.isEnabled(Flags.USE_APP_PANELS) &&
- activityTaskManagerProxy.supportsMultiWindow(context)) {
+ if (activityTaskManagerProxy.supportsMultiWindow(context)) {
val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
newServices.forEach {
it.resolvePanelActivity(allowAllApps) }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 3713811..a7e9efd8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -34,12 +34,9 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -60,8 +57,6 @@
private val controlsMetricsLogger: ControlsMetricsLogger,
private val vibrator: VibratorHelper,
private val controlsSettingsRepository: ControlsSettingsRepository,
- private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
- private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -77,9 +72,6 @@
}
override fun closeDialogs() {
- if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
- controlsSettingsDialogManager.closeDialog()
- }
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
if (isActivityFinishing == true) {
@@ -169,7 +161,6 @@
override fun runPendingAction(controlId: String) {
if (isLocked) return
if (pendingAction?.controlId == controlId) {
- showSettingsDialogIfNeeded(pendingAction!!)
pendingAction?.invoke()
pendingAction = null
}
@@ -208,7 +199,6 @@
true
}, { pendingAction = null }, true /* afterKeyguardGone */)
} else {
- showSettingsDialogIfNeeded(action)
action.invoke()
}
}
@@ -243,15 +233,6 @@
}
}
- private fun showSettingsDialogIfNeeded(action: Action) {
- if (action.authIsRequired) {
- return
- }
- if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
- controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
- }
- }
-
@VisibleForTesting
fun createAction(
controlId: String,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 557dcf4..8341964 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -36,7 +36,6 @@
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -66,9 +65,7 @@
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lastConfiguration.setTo(resources.configuration)
- if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
- }
+ window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
setContentView(R.layout.controls_fullscreen)
@@ -77,7 +74,7 @@
requireViewById(R.id.control_detail_root),
window,
intent,
- !featureFlags.isEnabled(Flags.USE_APP_PANELS)
+ false
)
)
@@ -114,7 +111,7 @@
parent = requireViewById(R.id.control_detail_root)
parent.alpha = 0f
- if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
+ if (!keyguardStateController.isUnlocked) {
controlsSettingsDialogManager.maybeShowDialog(this) {
uiController.show(parent, { finishOrReturnToDream() }, this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 25eae20..16bb75d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -506,30 +506,22 @@
val isPanel = selectedItem is SelectedItem.PanelItem
val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
?: EMPTY_STRUCTURE
- val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
val items = buildList {
add(OverflowMenuAdapter.MenuItem(
context.getText(R.string.controls_open_app),
OPEN_APP_ID
))
- if (newFlows || isPanel) {
- if (extraApps) {
- add(OverflowMenuAdapter.MenuItem(
- context.getText(R.string.controls_menu_add_another_app),
- ADD_APP_ID
- ))
- }
- if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
- add(OverflowMenuAdapter.MenuItem(
- context.getText(R.string.controls_menu_remove),
- REMOVE_APP_ID,
- ))
- }
- } else {
+ if (extraApps) {
add(OverflowMenuAdapter.MenuItem(
- context.getText(R.string.controls_menu_add),
- ADD_CONTROLS_ID
+ context.getText(R.string.controls_menu_add_another_app),
+ ADD_APP_ID
+ ))
+ }
+ if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+ add(OverflowMenuAdapter.MenuItem(
+ context.getText(R.string.controls_menu_remove),
+ REMOVE_APP_ID,
))
}
if (!isPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 553405f..5577cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
+import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
import android.content.ComponentName;
import android.content.Context;
@@ -161,7 +162,7 @@
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
- TouchInsetManager touchInsetManager,
+ @Named(DREAM_TOUCH_INSET_MANAGER) TouchInsetManager touchInsetManager,
@Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
ComponentName lowLightDreamComponent,
DreamOverlayCallbackController dreamOverlayCallbackController,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index c61b4775..4bafe32 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -31,11 +31,13 @@
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
+import com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Named;
@@ -55,7 +57,7 @@
String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
String DREAM_OVERLAY_SERVICE_COMPONENT = "dream_overlay_service_component";
String DREAM_OVERLAY_ENABLED = "dream_overlay_enabled";
-
+ String DREAM_TOUCH_INSET_MANAGER = "dream_touch_inset_manager";
String DREAM_SUPPORTED = "dream_supported";
String DREAM_OVERLAY_WINDOW_TITLE = "dream_overlay_window_title";
@@ -69,6 +71,15 @@
}
/**
+ * Provides a touch inset manager for dreams.
+ */
+ @Provides
+ @Named(DREAM_TOUCH_INSET_MANAGER)
+ static TouchInsetManager providesTouchInsetManager(@Main Executor executor) {
+ return new TouchInsetManager(executor);
+ }
+
+ /**
* Provides whether dream overlay is enabled.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f3339c0..90b27c7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -62,11 +62,12 @@
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
/**
- * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
- * enable it on release builds.
+ * This flag controls whether we register a listener for StatsD notification memory reports.
+ * For statsd to actually call the listener however, a server-side toggle needs to be
+ * enabled as well.
*/
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- unreleasedFlag(119, "notification_memory_logging_enabled")
+ releasedFlag(119, "notification_memory_logging_enabled")
// TODO(b/260335638): Tracking Bug
@JvmField
@@ -239,7 +240,7 @@
/** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
// TODO(b/279794160): Tracking bug.
- @JvmField val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer", teamfood = true)
+ @JvmField val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
/** Keyguard Migration */
@@ -282,6 +283,15 @@
// TODO(b/291767565): Tracking bug.
@JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
+ /** Enables preview loading animation in the wallpaper picker. */
+ // TODO(b/274443705): Tracking Bug
+ @JvmField
+ val WALLPAPER_PICKER_PREVIEW_ANIMATION =
+ unreleasedFlag(
+ 244,
+ "wallpaper_picker_preview_animation"
+ )
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -382,6 +392,9 @@
// 804 - monochromatic themes
@JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
+ // TODO(b/293380347): Tracking Bug
+ @JvmField val COLOR_FIDELITY = unreleasedFlag(805, "color_fidelity")
+
// 900 - media
// TODO(b/254512697): Tracking Bug
val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
@@ -613,13 +626,8 @@
@JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
// 2000 - device controls
- @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels")
-
@JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed")
- @JvmField
- val CONTROLS_MANAGEMENT_NEW_FLOWS = releasedFlag(2002, "controls_management_new_flows")
-
// Enables removing app from Home control panel as a part of a new flow
// TODO(b/269132640): Tracking Bug
@JvmField
@@ -701,7 +709,7 @@
// TODO(b/285059790) : Tracking Bug
@JvmField
val LOCKSCREEN_WALLPAPER_DREAM_ENABLED =
- unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream")
+ unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream", teamfood = true)
// TODO(b/283084712): Tracking Bug
@JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
@@ -731,4 +739,12 @@
// TODO(b/290213663): Tracking Bug
@JvmField
val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration")
+
+ /** Enable the Compose implementation of the PeopleSpaceActivity. */
+ @JvmField
+ val COMPOSE_PEOPLE_SPACE = unreleasedFlag(293570761, "compose_people_space")
+
+ /** Enable the Compose implementation of the Quick Settings footer actions. */
+ @JvmField
+ val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag(293569320, "compose_qs_footer_actions")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 82d0c8b..66de371 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -100,6 +100,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -173,6 +174,8 @@
import dagger.Lazy;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
@@ -257,6 +260,22 @@
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
+ /** Enum for reasons behind updating wakeAndUnlock state. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {
+ WakeAndUnlockUpdateReason.HIDE,
+ WakeAndUnlockUpdateReason.SHOW,
+ WakeAndUnlockUpdateReason.FULFILL,
+ WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK,
+ })
+ @interface WakeAndUnlockUpdateReason {
+ int HIDE = 0;
+ int SHOW = 1;
+ int FULFILL = 2;
+ int WAKE_AND_UNLOCK = 3;
+ }
+
/**
* The default amount of time we stay awake (used for all key input)
*/
@@ -808,7 +827,7 @@
// dreaming. It's time to wake up.
if (mUnlockingAndWakingFromDream) {
Log.d(TAG, "waking from dream after unlock");
- mUnlockingAndWakingFromDream = false;
+ setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.FULFILL);
if (mKeyguardStateController.isShowing()) {
Log.d(TAG, "keyguard showing after keyguardGone, dismiss");
@@ -2693,7 +2712,7 @@
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
- mUnlockingAndWakingFromDream = false;
+ setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW);
setPendingLock(false);
// Force if we we're showing in the middle of hiding, to ensure we end up in the correct
@@ -2799,6 +2818,51 @@
tryKeyguardDone();
};
+ private void setUnlockAndWakeFromDream(boolean updatedValue,
+ @WakeAndUnlockUpdateReason int reason) {
+ if (updatedValue == mUnlockingAndWakingFromDream) {
+ return;
+ }
+
+ final String reasonDescription;
+
+ switch(reason) {
+ case WakeAndUnlockUpdateReason.FULFILL:
+ reasonDescription = "fulfilling existing request";
+ break;
+ case WakeAndUnlockUpdateReason.HIDE:
+ reasonDescription = "hiding keyguard";
+ break;
+ case WakeAndUnlockUpdateReason.SHOW:
+ reasonDescription = "showing keyguard";
+ break;
+ case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
+ reasonDescription = "waking to unlock";
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + reason);
+ }
+
+ final boolean unsetUnfulfilled = !updatedValue
+ && reason != WakeAndUnlockUpdateReason.FULFILL;
+
+ mUnlockingAndWakingFromDream = updatedValue;
+
+ final String description;
+
+ if (unsetUnfulfilled) {
+ description = "Interrupting request to wake and unlock";
+ } else if (mUnlockingAndWakingFromDream) {
+ description = "Initiating request to wake and unlock";
+ } else {
+ description = "Fulfilling request to wake and unlock";
+ }
+
+ Log.d(TAG, String.format(
+ "Updating waking and unlocking request to %b. description:[%s]. reason:[%s]",
+ mUnlockingAndWakingFromDream, description, reasonDescription));
+ }
+
/**
* Handle message sent by {@link #hideLocked()}
* @see #HIDE
@@ -2816,8 +2880,11 @@
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
- mUnlockingAndWakingFromDream = mStatusBarStateController.isDreaming()
- && !mStatusBarStateController.isDozing();
+ // If waking and unlocking, waking from dream has been set properly.
+ if (!mWakeAndUnlocking) {
+ setUnlockAndWakeFromDream(mStatusBarStateController.isDreaming()
+ && mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE);
+ }
if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) {
if (mUnlockingAndWakingFromDream) {
@@ -3224,7 +3291,8 @@
flags |= StatusBarManager.DISABLE_RECENT;
}
- if (mPowerGestureIntercepted && mOccluded && isSecure()) {
+ if (mPowerGestureIntercepted && mOccluded && isSecure()
+ && mUpdateMonitor.isFaceEnrolled()) {
flags |= StatusBarManager.DISABLE_RECENT;
}
@@ -3319,9 +3387,14 @@
}
}
- public void onWakeAndUnlocking() {
+ /**
+ * Informs the keyguard view mediator that the device is waking and unlocking.
+ * @param fromDream Whether waking and unlocking is happening over an interactive dream.
+ */
+ public void onWakeAndUnlocking(boolean fromDream) {
Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
mWakeAndUnlocking = true;
+ setUnlockAndWakeFromDream(fromDream, WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK);
mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* primaryAuth */ false);
userActivity();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 6fd3e21..30f8f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -110,6 +110,18 @@
fun lockoutFaceAuth()
/**
+ * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
+ * invoked.
+ */
+ fun pauseFaceAuth()
+
+ /**
+ * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
+ * [authenticate] will run as long as other gating conditions don't stop it from running.
+ */
+ fun resumeFaceAuth()
+
+ /**
* Trigger face authentication.
*
* [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
@@ -186,6 +198,15 @@
override val isAuthRunning: StateFlow<Boolean>
get() = _isAuthRunning
+ private val faceAuthPaused = MutableStateFlow(false)
+ override fun pauseFaceAuth() {
+ faceAuthPaused.value = true
+ }
+
+ override fun resumeFaceAuth() {
+ faceAuthPaused.value = false
+ }
+
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
@@ -329,11 +350,7 @@
"isFaceAuthenticationEnabled",
tableLogBuffer
),
- logAndObserve(
- userRepository.userSwitchingInProgress.isFalse(),
- "userSwitchingNotInProgress",
- tableLogBuffer
- ),
+ logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
logAndObserve(
keyguardRepository.isKeyguardGoingAway.isFalse(),
"keyguardNotGoingAway",
@@ -454,7 +471,6 @@
}
private fun handleFaceCancellationError() {
- cancelNotReceivedHandlerJob?.cancel()
applicationScope.launch {
faceAuthRequestedWhileCancellation?.let {
faceAuthLogger.launchingQueuedFaceAuthRequest(it)
@@ -483,6 +499,7 @@
}
private fun onFaceAuthRequestCompleted() {
+ cancelNotReceivedHandlerJob?.cancel()
cancellationInProgress = false
_isAuthRunning.value = false
authCancellationSignal = null
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 9ee9902..f1b3441 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -47,16 +47,15 @@
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
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.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
@@ -177,7 +176,7 @@
val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
/** Receive an event for doze time tick */
- val dozeTimeTick: Flow<Unit>
+ val dozeTimeTick: Flow<Long>
/**
* Returns `true` if the keyguard is showing; `false` otherwise.
@@ -248,6 +247,7 @@
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
+ private val systemClock: SystemClock,
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -398,11 +398,11 @@
_isDozing.value = isDozing
}
- private val _dozeTimeTick = MutableSharedFlow<Unit>()
- override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+ private val _dozeTimeTick = MutableStateFlow<Long>(0)
+ override val dozeTimeTick = _dozeTimeTick.asStateFlow()
override fun dozeTimeTick() {
- _dozeTimeTick.tryEmit(Unit)
+ _dozeTimeTick.value = systemClock.uptimeMillis()
}
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index 5ef9a9e..e4e6a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,9 @@
get() = emptyFlow()
override fun lockoutFaceAuth() = Unit
+ override fun pauseFaceAuth() = Unit
+
+ override fun resumeFaceAuth() = Unit
/**
* Trigger face authentication.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 252982f..ac936b1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -24,14 +24,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
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.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -46,24 +43,17 @@
private val burnInHelperWrapper: BurnInHelperWrapper,
@Application private val scope: CoroutineScope,
private val configurationRepository: ConfigurationRepository,
- private val systemClock: SystemClock,
+ private val keyguardInteractor: KeyguardInteractor,
) {
- private val _dozeTimeTick = MutableStateFlow<Long>(0)
- val dozeTimeTick: StateFlow<Long> = _dozeTimeTick.asStateFlow()
-
val udfpsBurnInXOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
val udfpsBurnInYOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
val udfpsBurnInProgress: StateFlow<Float> =
- dozeTimeTick
+ keyguardInteractor.dozeTimeTick
.mapLatest { burnInHelperWrapper.burnInProgressOffset() }
.stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
- fun dozeTimeTick() {
- _dozeTimeTick.value = systemClock.uptimeMillis()
- }
-
/**
* Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
* max burn-in offset on any configuration changes. If the max burn-in offset is specified in
@@ -77,7 +67,9 @@
.flatMapLatest {
val maxBurnInOffsetPixels =
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis) }
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis)
+ }
}
.stateIn(
scope,
@@ -102,7 +94,9 @@
.flatMapLatest { scale ->
val maxBurnInOffsetPixels =
context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- dozeTimeTick.mapLatest { calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) }
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
+ }
}
.stateIn(
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
new file mode 100644
index 0000000..dac6ef5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class FromDreamingLockscreenHostedTransitionInteractor
+@Inject
+constructor(
+ override val transitionRepository: KeyguardTransitionRepository,
+ override val transitionInteractor: KeyguardTransitionInteractor,
+ @Application private val scope: CoroutineScope,
+ private val keyguardInteractor: KeyguardInteractor,
+) :
+ TransitionInteractor(
+ fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
+ ) {
+
+ override fun start() {
+ listenForDreamingLockscreenHostedToLockscreen()
+ listenForDreamingLockscreenHostedToGone()
+ listenForDreamingLockscreenHostedToDozing()
+ listenForDreamingLockscreenHostedToOccluded()
+ listenForDreamingLockscreenHostedToPrimaryBouncer()
+ }
+
+ private fun listenForDreamingLockscreenHostedToLockscreen() {
+ scope.launch {
+ keyguardInteractor.isActiveDreamLockscreenHosted
+ // Add a slight delay to prevent transitioning to lockscreen from happening too soon
+ // as dozing can arrive in a slight gap after the lockscreen hosted dream stops.
+ .onEach { delay(50) }
+ .sample(
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect {
+ (isActiveDreamLockscreenHosted, dozeTransitionModel, lastStartedTransition) ->
+ if (
+ !isActiveDreamLockscreenHosted &&
+ DozeStateModel.isDozeOff(dozeTransitionModel.to) &&
+ lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+ ) {
+ startTransitionTo(KeyguardState.LOCKSCREEN)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingLockscreenHostedToOccluded() {
+ scope.launch {
+ keyguardInteractor.isActiveDreamLockscreenHosted
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair,
+ ),
+ ::toTriple
+ )
+ .collect { (isActiveDreamLockscreenHosted, isOccluded, lastStartedTransition) ->
+ if (
+ isOccluded &&
+ !isActiveDreamLockscreenHosted &&
+ lastStartedTransition.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+ ) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingLockscreenHostedToPrimaryBouncer() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (
+ isBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+ ) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingLockscreenHostedToGone() {
+ scope.launch {
+ keyguardInteractor.biometricUnlockState
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (biometricUnlockState, lastStartedTransitionStep) ->
+ if (
+ lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED &&
+ biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ ) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingLockscreenHostedToDozing() {
+ scope.launch {
+ combine(
+ keyguardInteractor.dozeTransitionModel,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ )
+ .collect { (dozeTransitionModel, lastStartedTransitionStep) ->
+ if (
+ dozeTransitionModel.to == DozeStateModel.DOZE &&
+ lastStartedTransitionStep.to == KeyguardState.DREAMING_LOCKSCREEN_HOSTED
+ ) {
+ startTransitionTo(KeyguardState.DOZING)
+ }
+ }
+ }
+ }
+
+ override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
+ return ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration =
+ if (toState == KeyguardState.LOCKSCREEN) TO_LOCKSCREEN_DURATION.inWholeMilliseconds
+ else DEFAULT_DURATION.inWholeMilliseconds
+ }
+ }
+
+ companion object {
+ private val DEFAULT_DURATION = 500.milliseconds
+ val TO_LOCKSCREEN_DURATION = 1167.milliseconds
+ }
+}
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 98d7434..954ff6f 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
@@ -94,11 +94,16 @@
private fun listenForDreamingToGone() {
scope.launch {
- keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
- if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
- startTransitionTo(KeyguardState.GONE)
+ keyguardInteractor.biometricUnlockState
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (biometricUnlockState, lastStartedTransitionStep) ->
+ if (
+ lastStartedTransitionStep.to == KeyguardState.DREAMING &&
+ biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ ) {
+ startTransitionTo(KeyguardState.GONE)
+ }
}
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index f82633f..2b08b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -48,6 +48,7 @@
listenForGoneToAodOrDozing()
listenForGoneToDreaming()
listenForGoneToLockscreen()
+ listenForGoneToDreamingLockscreenHosted()
}
// Primarily for when the user chooses to lock down the device
@@ -63,12 +64,35 @@
}
}
+ private fun listenForGoneToDreamingLockscreenHosted() {
+ scope.launch {
+ keyguardInteractor.isActiveDreamLockscreenHosted
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isActiveDreamLockscreenHosted, lastStartedStep) ->
+ if (isActiveDreamLockscreenHosted && lastStartedStep.to == KeyguardState.GONE) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ }
+ }
+ }
+ }
+
private fun listenForGoneToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
- .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { (isAbleToDream, lastStartedStep) ->
- if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
+ .sample(
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isAbleToDream, lastStartedStep, isActiveDreamLockscreenHosted) ->
+ if (
+ isAbleToDream &&
+ lastStartedStep.to == KeyguardState.GONE &&
+ !isActiveDreamLockscreenHosted
+ ) {
startTransitionTo(KeyguardState.DREAMING)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index ed1bf3e..6b28b27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -70,17 +70,27 @@
combine(
transitionInteractor.startedKeyguardTransitionStep,
transitionInteractor.finishedKeyguardState,
- ::Pair
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ ::Triple
),
- ::toTriple
+ ::toQuad
)
- .collect { (isAbleToDream, lastStartedTransition, finishedKeyguardState) ->
+ .collect {
+ (
+ isAbleToDream,
+ lastStartedTransition,
+ finishedKeyguardState,
+ isActiveDreamLockscreenHosted) ->
val isOnLockscreen = finishedKeyguardState == KeyguardState.LOCKSCREEN
val isTransitionInterruptible =
lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
!invalidFromStates.contains(lastStartedTransition.from)
if (isAbleToDream && (isOnLockscreen || isTransitionInterruptible)) {
- startTransitionTo(KeyguardState.DREAMING)
+ if (isActiveDreamLockscreenHosted) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ } else {
+ startTransitionTo(KeyguardState.DREAMING)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index e1754f5..9142d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -27,6 +27,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -52,6 +54,7 @@
listenForPrimaryBouncerToGone()
listenForPrimaryBouncerToAodOrDozing()
listenForPrimaryBouncerToLockscreenOrOccluded()
+ listenForPrimaryBouncerToDreamingLockscreenHosted()
}
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -62,17 +65,24 @@
keyguardInteractor.wakefulnessModel,
transitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.isKeyguardOccluded,
- ::Triple
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ ::toQuad
),
- ::toQuad
+ ::toQuint
)
- .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep, occluded)
- ->
+ .collect {
+ (
+ isBouncerShowing,
+ wakefulnessState,
+ lastStartedTransitionStep,
+ occluded,
+ isActiveDreamLockscreenHosted) ->
if (
!isBouncerShowing &&
lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
(wakefulnessState.state == WakefulnessState.AWAKE ||
- wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE)
+ wakefulnessState.state == WakefulnessState.STARTING_TO_WAKE) &&
+ !isActiveDreamLockscreenHosted
) {
startTransitionTo(
if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
@@ -111,6 +121,30 @@
}
}
+ private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ combine(
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect {
+ (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+ if (
+ !isBouncerShowing &&
+ isActiveDreamLockscreenHosted &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ }
+ }
+ }
+ }
+
private fun listenForPrimaryBouncerToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index cc15916..53d3c07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -34,9 +34,9 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.ScreenModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -87,7 +87,7 @@
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
/** Receive an event for doze time tick */
- val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
+ val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
/** Whether Always-on Display mode is available. */
val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
/** Doze transition information. */
@@ -100,7 +100,7 @@
/** Whether the system is dreaming with an overlay active */
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
/** Whether the system is dreaming and the active dream is hosted in lockscreen */
- val isActiveDreamLockscreenHosted: Flow<Boolean> = repository.isActiveDreamLockscreenHosted
+ val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
val callback =
@@ -122,6 +122,9 @@
/** The device wake/sleep state */
val wakefulnessModel: StateFlow<WakefulnessModel> = repository.wakefulness
+ /** The device screen state */
+ val screenModel: StateFlow<ScreenModel> = repository.screenModel
+
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
@@ -237,8 +240,16 @@
repository.setQuickSettingsVisible(isVisible)
}
- fun setKeyguardRootVisibility(statusBarState: Int, goingToFullShade: Boolean, isOcclusionTransitionRunning: Boolean) {
- repository.setKeyguardVisibility(statusBarState, goingToFullShade, isOcclusionTransitionRunning)
+ fun setKeyguardRootVisibility(
+ statusBarState: Int,
+ goingToFullShade: Boolean,
+ isOcclusionTransitionRunning: Boolean
+ ) {
+ repository.setKeyguardVisibility(
+ statusBarState,
+ goingToFullShade,
+ isOcclusionTransitionRunning
+ )
}
fun setClockPosition(x: Int, y: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index a486843..9c796f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -412,7 +412,11 @@
KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_PAGE_TRANSITIONS,
value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PAGE_TRANSITIONS)
- )
+ ),
+ KeyguardPickerFlag(
+ name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION,
+ value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_PREVIEW_ANIMATION)
+ ),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index efc1bd0..ba7b987 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -45,6 +45,7 @@
is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
is FromAlternateBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromDreamingLockscreenHostedTransitionInteractor -> Log.d(TAG, "Started $it")
}
it.start()
}
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 45bf20d..8c4c7ae 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
@@ -24,6 +24,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
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.OCCLUDED
@@ -89,12 +90,20 @@
val dreamingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DREAMING, LOCKSCREEN)
+ /** DREAMING_LOCKSCREEN_HOSTED->LOCKSCREEN transition information. */
+ val dreamingLockscreenHostedToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)
+
/** GONE->AOD transition information. */
val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
/** GONE->DREAMING transition information. */
val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
+ /** GONE->DREAMING_LOCKSCREEN_HOSTED transition information. */
+ val goneToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
+ repository.transition(GONE, DREAMING_LOCKSCREEN_HOSTED)
+
/** LOCKSCREEN->AOD transition information. */
val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
@@ -102,6 +111,10 @@
val lockscreenToDreamingTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, DREAMING)
+ /** LOCKSCREEN->DREAMING_LOCKSCREEN_HOSTED transition information. */
+ val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
+ repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
+
/** LOCKSCREEN->OCCLUDED transition information. */
val lockscreenToOccludedTransition: Flow<TransitionStep> =
repository.transition(LOCKSCREEN, OCCLUDED)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 4244e55..6115d90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -92,6 +92,7 @@
KeyguardState.DOZING -> false
KeyguardState.AOD -> false
KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
KeyguardState.ALTERNATE_BOUNCER -> true
KeyguardState.PRIMARY_BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index 1c200b0..278c68d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -19,10 +19,9 @@
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.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -30,17 +29,14 @@
import kotlinx.coroutines.flow.stateIn
/** Hosts business and application state accessing logic for the lockscreen scene. */
+@SysUISingleton
class LockscreenSceneInteractor
-@AssistedInject
+@Inject
constructor(
@Application applicationScope: CoroutineScope,
private val authenticationInteractor: AuthenticationInteractor,
- bouncerInteractorFactory: BouncerInteractor.Factory,
- @Assisted private val containerName: String,
+ private val bouncerInteractor: BouncerInteractor,
) {
- private val bouncerInteractor: BouncerInteractor =
- bouncerInteractorFactory.create(containerName)
-
/** Whether the device is currently locked. */
val isDeviceLocked: StateFlow<Boolean> =
authenticationInteractor.isUnlocked
@@ -67,13 +63,6 @@
/** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
fun dismissLockscreen() {
- bouncerInteractor.showOrUnlockDevice(containerName = containerName)
- }
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): LockscreenSceneInteractor
+ bouncerInteractor.showOrUnlockDevice()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index d9690b7..56f5529 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -50,6 +50,12 @@
@Binds
@IntoSet
+ abstract fun fromDreamingLockscreenHosted(
+ impl: FromDreamingLockscreenHostedTransitionInteractor
+ ): TransitionInteractor
+
+ @Binds
+ @IntoSet
abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index 8f4776f..2a3f852 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -49,6 +50,7 @@
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* Encapsulates business logic related face authentication being triggered for device entry from
@@ -69,6 +71,7 @@
private val faceAuthenticationLogger: FaceAuthenticationLogger,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+ private val userRepository: UserRepository,
) : CoreStartable, KeyguardFaceAuthInteractor {
private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -128,6 +131,23 @@
}
}
.launchIn(applicationScope)
+
+ // User switching should stop face auth and then when it is complete we should trigger face
+ // auth so that the switched user can unlock the device with face auth.
+ userRepository.userSwitchingInProgress
+ .pairwise(false)
+ .onEach { (wasSwitching, isSwitching) ->
+ if (!wasSwitching && isSwitching) {
+ repository.pauseFaceAuth()
+ } else if (wasSwitching && !isSwitching) {
+ repository.resumeFaceAuth()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
+ fallbackToDetect = true
+ )
+ }
+ }
+ .launchIn(applicationScope)
}
override fun onSwipeUpOnBouncer() {
@@ -199,8 +219,10 @@
} else {
faceAuthenticationStatusOverride.value = null
applicationScope.launch {
- faceAuthenticationLogger.authRequested(uiEvent)
- repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+ withContext(mainDispatcher) {
+ faceAuthenticationLogger.authRequested(uiEvent)
+ repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+ }
}
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index 87b4321..1e20cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -35,6 +35,12 @@
* parties to present their own UI over keyguard, like a screensaver.
*/
DREAMING,
+ /*
+ * A device state after the device times out, which can be from both LOCKSCREEN or GONE states.
+ * It is a special version of DREAMING state but not DOZING. The active dream will be windowless
+ * and hosted in the lockscreen.
+ */
+ DREAMING_LOCKSCREEN_HOSTED,
/**
* The device has entered a special low-power mode within SystemUI, also called the Always-on
* Display (AOD). A minimal UI is presented to show critical information. If the device is in
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
new file mode 100644
index 0000000..113f01c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down GONE->DREAMING_LOCKSCREEN_HOSTED transition into discrete steps for corresponding
+ * views to consume.
+ */
+@SysUISingleton
+class GoneToDreamingLockscreenHostedTransitionViewModel
+@Inject
+constructor(
+ interactor: KeyguardTransitionInteractor,
+) {
+
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_DREAMING_DURATION,
+ transitionFlow = interactor.goneToDreamingLockscreenHostedTransition,
+ )
+
+ /** Lockscreen views alpha - hide immediately */
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.createFlow(
+ duration = 1.milliseconds,
+ onStep = { 0f },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index f212a55..abd178c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -19,12 +19,11 @@
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -32,15 +31,13 @@
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
+@SysUISingleton
class LockscreenSceneViewModel
-@AssistedInject
+@Inject
constructor(
@Application applicationScope: CoroutineScope,
- interactorFactory: LockscreenSceneInteractor.Factory,
- @Assisted containerName: String,
+ private val interactor: LockscreenSceneInteractor,
) {
- private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName)
-
/** The icon for the "lock" button on the lockscreen. */
val lockButtonIcon: StateFlow<Icon> =
interactor.isDeviceLocked
@@ -98,11 +95,4 @@
)
)
}
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): LockscreenSceneViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
index b307f1b..dd58607 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -112,6 +112,7 @@
KeyguardState.OFF,
KeyguardState.DOZING,
KeyguardState.DREAMING,
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
KeyguardState.AOD,
KeyguardState.PRIMARY_BOUNCER,
KeyguardState.GONE,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 42164c7..fdb3ddd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -30,16 +30,11 @@
import android.os.ResultReceiver
import android.os.UserHandle
import android.view.ViewGroup
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider
+import com.android.intentresolver.ChooserActivity
+import com.android.intentresolver.chooser.TargetInfo
import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
-import com.android.internal.app.ChooserActivity
-import com.android.internal.app.ResolverListController
-import com.android.internal.app.chooser.NotSelectableTargetInfo
-import com.android.internal.app.chooser.TargetInfo
import com.android.systemui.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
@@ -56,12 +51,8 @@
private val activityLauncher: AsyncActivityLauncher,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
- private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) :
- ChooserActivity(),
- MediaProjectionAppSelectorView,
- MediaProjectionAppSelectorResultHandler,
- LifecycleOwner {
+ private val listControllerFactory: ((userHandle: UserHandle) -> ChooserListController)?
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
@Inject
constructor(
@@ -69,8 +60,6 @@
activityLauncher: AsyncActivityLauncher
) : this(componentFactory, activityLauncher, listControllerFactory = null)
- private val lifecycleRegistry = LifecycleRegistry(this)
- override val lifecycle = lifecycleRegistry
private lateinit var configurationController: ConfigurationController
private lateinit var controller: MediaProjectionAppSelectorController
private lateinit var recentsViewController: MediaProjectionRecentsViewController
@@ -84,7 +73,6 @@
override fun getLayoutResource() = R.layout.media_projection_app_selector
public override fun onCreate(bundle: Bundle?) {
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
component = componentFactory.create(view = this, resultHandler = this)
component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
@@ -107,26 +95,6 @@
controller.init()
}
- override fun onStart() {
- super.onStart()
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
- }
-
- override fun onResume() {
- super.onResume()
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
- }
-
- override fun onPause() {
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
- super.onPause()
- }
-
- override fun onStop() {
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
- super.onStop()
- }
-
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
configurationController.onConfigurationChanged(newConfig)
@@ -137,13 +105,13 @@
override fun createBlockerEmptyStateProvider(): EmptyStateProvider =
component.emptyStateProvider
- override fun createListController(userHandle: UserHandle): ResolverListController =
+ override fun createListController(userHandle: UserHandle): ChooserListController =
listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
- if (targetInfo is NotSelectableTargetInfo) return
+ if (targetInfo.isNotSelectableTargetInfo) return
val intent = createIntent(targetInfo)
@@ -183,7 +151,6 @@
}
override fun onDestroy() {
- lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
component.lifecycleObservers.forEach { lifecycle.removeObserver(it) }
// onDestroy is also called when an app is selected, in that case we only want to send
// RECORD_CONTENT_TASK but not RECORD_CANCEL
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index f908481..785a1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -18,8 +18,8 @@
import android.content.Context
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager
-import com.android.settingslib.media.ManagerInfoMediaManager
import javax.inject.Inject
/** Factory to create [LocalMediaManager] objects. */
@@ -31,8 +31,7 @@
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String): LocalMediaManager {
- return ManagerInfoMediaManager(context, packageName, null, localBluetoothManager).run {
- LocalMediaManager(context, localBluetoothManager, this, packageName)
- }
+ return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+ .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 83631b0..be42569 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -76,7 +76,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.ManagerInfoMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
@@ -194,7 +193,7 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = new ManagerInfoMediaManager(mContext, packageName, null, lbm);
+ InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
index 829b0dd..fd14e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -17,10 +17,10 @@
import android.content.Context
import android.os.UserHandle
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.intentresolver.ResolverListAdapter
import com.android.internal.R as AndroidR
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
-import com.android.internal.app.ResolverListAdapter
import com.android.systemui.R
import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 7f0f894..d1d3e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -28,6 +28,8 @@
import androidx.lifecycle.ViewModelProvider;
import com.android.systemui.compose.ComposeFacade;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.people.ui.view.PeopleViewBinder;
import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
@@ -43,11 +45,14 @@
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private final PeopleViewModel.Factory mViewModelFactory;
+ private final FeatureFlags mFeatureFlags;
@Inject
- public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
+ public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory,
+ FeatureFlags featureFlags) {
super();
mViewModelFactory = viewModelFactory;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -67,7 +72,8 @@
return null;
};
- if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+ if (mFeatureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)
+ && ComposeFacade.INSTANCE.isComposeAvailable()) {
Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index a2b2a89..d801faa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,6 +52,7 @@
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -285,7 +286,8 @@
private void bindFooterActionsView(View root) {
LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
- if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
+ if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)
+ || !ComposeFacade.INSTANCE.isComposeAvailable()) {
Log.d(TAG, "Binding the View implementation of the QS footer actions");
mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
mListeningAndVisibilityLifecycleOwner);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 3e7bdd1..9bb192b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -34,6 +34,7 @@
import android.service.notification.ZenModeConfig;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
import android.widget.Switch;
@@ -315,6 +316,7 @@
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
public void onZenChanged(int zen) {
+ Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile.");
refreshState(zen);
}
};
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 36dec1d..5e6a44b 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
@@ -16,30 +16,19 @@
package com.android.systemui.qs.ui.viewmodel
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
/** Models UI state and handles user input for the quick settings scene. */
+@SysUISingleton
class QuickSettingsSceneViewModel
-@AssistedInject
+@Inject
constructor(
- lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
- @Assisted containerName: String,
+ private val lockscreenSceneInteractor: LockscreenSceneInteractor,
) {
- private val lockscreenSceneInteractor: LockscreenSceneInteractor =
- lockscreenSceneInteractorFactory.create(containerName)
-
/** Notifies that some content in quick settings was clicked. */
fun onContentClicked() {
lockscreenSceneInteractor.dismissLockscreen()
}
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): QuickSettingsSceneViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index bf40a2d..03bd11b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -97,7 +97,6 @@
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneContainerNames;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
@@ -221,8 +220,7 @@
// If scene framework is enabled, set the scene container window to
// visible and let the touch "slip" into that window.
if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
- mSceneInteractor.get().setVisible(
- SceneContainerNames.SYSTEM_UI_DEFAULT, true);
+ mSceneInteractor.get().setVisible(true);
} else {
centralSurfaces.onInputFocusTransfer(
mInputFocusTransferStarted, false /* cancel */,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0a9839e..398e64b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -16,16 +16,16 @@
package com.android.systemui.scene
+import com.android.systemui.scene.domain.startable.SceneContainerStartableModule
import com.android.systemui.scene.shared.model.SceneContainerConfigModule
import com.android.systemui.scene.ui.composable.SceneModule
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModelModule
import dagger.Module
@Module(
includes =
[
SceneContainerConfigModule::class,
- SceneContainerViewModelModule::class,
+ SceneContainerStartableModule::class,
SceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 0a86d35..1fca488 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -29,26 +29,20 @@
class SceneContainerRepository
@Inject
constructor(
- private val containerConfigByName: Map<String, SceneContainerConfig>,
+ private val config: SceneContainerConfig,
) {
- private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> =
- containerConfigByName
- .map { (containerName, _) -> containerName to MutableStateFlow(true) }
- .toMap()
- private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> =
- containerConfigByName
- .map { (containerName, config) ->
- containerName to MutableStateFlow(SceneModel(config.initialSceneKey))
- }
- .toMap()
- private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> =
- containerConfigByName
- .map { (containerName, _) -> containerName to MutableStateFlow(1f) }
- .toMap()
- private val sceneTransitionByContainerName:
- Map<String, MutableStateFlow<SceneTransitionModel?>> =
- containerConfigByName.keys.associateWith { MutableStateFlow(null) }
+ private val _isVisible = MutableStateFlow(true)
+ val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
+
+ private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey))
+ val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow()
+
+ private val _transitionProgress = MutableStateFlow(1f)
+ val transitionProgress: StateFlow<Float> = _transitionProgress.asStateFlow()
+
+ private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
+ val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
/**
* Returns the keys to all scenes in the container with the given name.
@@ -56,100 +50,50 @@
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
- fun allSceneKeys(containerName: String): List<SceneKey> {
- return containerConfigByName[containerName]?.sceneKeys
- ?: error(noSuchContainerErrorMessage(containerName))
+ fun allSceneKeys(): List<SceneKey> {
+ return config.sceneKeys
}
/** Sets the current scene in the container with the given name. */
- fun setCurrentScene(containerName: String, scene: SceneModel) {
- check(allSceneKeys(containerName).contains(scene.key)) {
+ fun setCurrentScene(scene: SceneModel) {
+ check(allSceneKeys().contains(scene.key)) {
"""
- Cannot set current scene key to "${scene.key}". The container "$containerName" does
- not contain a scene with that key.
+ Cannot set current scene key to "${scene.key}". The configuration does not contain a
+ scene with that key.
"""
.trimIndent()
}
- currentSceneByContainerName.setValue(containerName, scene)
+ _currentScene.value = scene
}
/** Sets the scene transition in the container with the given name. */
- fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
- check(allSceneKeys(containerName).contains(from)) {
+ fun setSceneTransition(from: SceneKey, to: SceneKey) {
+ check(allSceneKeys().contains(from)) {
"""
- Cannot set current scene key to "$from". The container "$containerName" does
- not contain a scene with that key.
+ Cannot set current scene key to "$from". The configuration does not contain a scene
+ with that key.
"""
.trimIndent()
}
- check(allSceneKeys(containerName).contains(to)) {
+ check(allSceneKeys().contains(to)) {
"""
- Cannot set current scene key to "$to". The container "$containerName" does
- not contain a scene with that key.
+ Cannot set current scene key to "$to". The configuration does not contain a scene
+ with that key.
"""
.trimIndent()
}
- sceneTransitionByContainerName.setValue(
- containerName,
- SceneTransitionModel(from = from, to = to)
- )
- }
-
- /** The current scene in the container with the given name. */
- fun currentScene(containerName: String): StateFlow<SceneModel> {
- return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
- }
-
- /**
- * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
- * transition occurs. The flow begins with a `null` value at first, because the initial scene is
- * not something that we transition to from another scene.
- */
- fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
- return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
+ _transitions.value = SceneTransitionModel(from = from, to = to)
}
/** Sets whether the container with the given name is visible. */
- fun setVisible(containerName: String, isVisible: Boolean) {
- containerVisibilityByName.setValue(containerName, isVisible)
- }
-
- /** Whether the container with the given name should be visible. */
- fun isVisible(containerName: String): StateFlow<Boolean> {
- return containerVisibilityByName.mutableOrError(containerName).asStateFlow()
+ fun setVisible(isVisible: Boolean) {
+ _isVisible.value = isVisible
}
/** Sets scene transition progress to the current scene in the container with the given name. */
- fun setSceneTransitionProgress(containerName: String, progress: Float) {
- sceneTransitionProgressByContainerName.setValue(containerName, progress)
- }
-
- /** Progress of the transition into the current scene in the container with the given name. */
- fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
- return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow()
- }
-
- private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError(
- containerName: String,
- ): MutableStateFlow<T> {
- return this[containerName] ?: error(noSuchContainerErrorMessage(containerName))
- }
-
- private fun <T> Map<String, MutableStateFlow<T>>.setValue(
- containerName: String,
- value: T,
- ) {
- val mutable = mutableOrError(containerName)
- mutable.value = value
- }
-
- private fun noSuchContainerErrorMessage(containerName: String): String {
- return """
- No container named "$containerName". Existing containers:
- ${containerConfigByName.values.joinToString(", ") { it.name }}
- """
- .trimIndent()
+ fun setSceneTransitionProgress(progress: Float) {
+ _transitionProgress.value = progress
}
}
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 f03f040..39daad3 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
@@ -30,12 +30,8 @@
/**
* Generic business logic and app state accessors for the scene framework.
*
- * Note that scene container specific business logic does not belong in this class. Instead, it
- * should be hoisted to a class that is specific to that scene container, for an example, please see
- * [SystemUiDefaultSceneContainerStartable].
- *
- * Also note that this class should not depend on state or logic of other modules or features.
- * Instead, other feature modules should depend on and call into this class when their parts of the
+ * Note that this class should not depend on state or logic of other modules or features. Instead,
+ * other feature modules should depend on and call into this class when their parts of the
* application state change.
*/
@SysUISingleton
@@ -51,50 +47,42 @@
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
- fun allSceneKeys(containerName: String): List<SceneKey> {
- return repository.allSceneKeys(containerName)
+ fun allSceneKeys(): List<SceneKey> {
+ return repository.allSceneKeys()
}
/** Sets the scene in the container with the given name. */
- fun setCurrentScene(containerName: String, scene: SceneModel) {
- val currentSceneKey = repository.currentScene(containerName).value.key
- repository.setCurrentScene(containerName, scene)
- repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
+ fun setCurrentScene(scene: SceneModel) {
+ val currentSceneKey = repository.currentScene.value.key
+ repository.setCurrentScene(scene)
+ repository.setSceneTransition(from = currentSceneKey, to = scene.key)
}
/** The current scene in the container with the given name. */
- fun currentScene(containerName: String): StateFlow<SceneModel> {
- return repository.currentScene(containerName)
- }
+ val currentScene: StateFlow<SceneModel> = repository.currentScene
/** Sets the visibility of the container with the given name. */
- fun setVisible(containerName: String, isVisible: Boolean) {
- return repository.setVisible(containerName, isVisible)
+ fun setVisible(isVisible: Boolean) {
+ return repository.setVisible(isVisible)
}
/** Whether the container with the given name is visible. */
- fun isVisible(containerName: String): StateFlow<Boolean> {
- return repository.isVisible(containerName)
- }
+ val isVisible: StateFlow<Boolean> = repository.isVisible
/** Sets scene transition progress to the current scene in the container with the given name. */
- fun setSceneTransitionProgress(containerName: String, progress: Float) {
- repository.setSceneTransitionProgress(containerName, progress)
+ fun setSceneTransitionProgress(progress: Float) {
+ repository.setSceneTransitionProgress(progress)
}
/** Progress of the transition into the current scene in the container with the given name. */
- fun sceneTransitionProgress(containerName: String): StateFlow<Float> {
- return repository.sceneTransitionProgress(containerName)
- }
+ val transitionProgress: StateFlow<Float> = repository.transitionProgress
/**
* Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
* transition occurs. The flow begins with a `null` value at first, because the initial scene is
* not something that we transition to from another scene.
*/
- fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
- return repository.sceneTransitions(containerName)
- }
+ val transitions: StateFlow<SceneTransitionModel?> = repository.transitions
private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
rename to packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 92384d6..1c87eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -28,7 +28,6 @@
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
@@ -44,12 +43,11 @@
import kotlinx.coroutines.launch
/**
- * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
- * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
- * from other systems.
+ * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
+ * scene container based on state from other systems.
*/
@SysUISingleton
-class SystemUiDefaultSceneContainerStartable
+class SceneContainerStartable
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
@@ -72,13 +70,10 @@
/** Updates the visibility of the scene container based on the current scene. */
private fun hydrateVisibility() {
applicationScope.launch {
- sceneInteractor
- .currentScene(CONTAINER_NAME)
+ sceneInteractor.currentScene
.map { it.key }
.distinctUntilChanged()
- .collect { sceneKey ->
- sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
- }
+ .collect { sceneKey -> sceneInteractor.setVisible(sceneKey != SceneKey.Gone) }
}
}
@@ -87,7 +82,7 @@
applicationScope.launch {
authenticationInteractor.isUnlocked
.map { isUnlocked ->
- val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
+ val currentSceneKey = sceneInteractor.currentScene.value.key
val isBypassEnabled = authenticationInteractor.isBypassEnabled()
when {
isUnlocked ->
@@ -135,8 +130,7 @@
/** Keeps [SysUiState] up-to-date */
private fun hydrateSystemUiState() {
applicationScope.launch {
- sceneInteractor
- .currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT)
+ sceneInteractor.currentScene
.map { it.key }
.distinctUntilChanged()
.collect { sceneKey ->
@@ -155,12 +149,7 @@
private fun switchToScene(targetSceneKey: SceneKey) {
sceneInteractor.setCurrentScene(
- containerName = CONTAINER_NAME,
scene = SceneModel(targetSceneKey),
)
}
-
- companion object {
- private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
index b3de2d1..8da1803 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -27,6 +27,6 @@
@Binds
@IntoMap
- @ClassKey(SystemUiDefaultSceneContainerStartable::class)
- fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
+ @ClassKey(SceneContainerStartable::class)
+ fun bind(impl: SceneContainerStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 354de8a..31597c1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -27,8 +27,6 @@
* takes care of rendering the current scene and allowing scenes to be switched from one to another
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
- *
- * The framework also supports multiple containers, each one with its own configuration.
*/
interface Scene {
@@ -59,7 +57,7 @@
* The API is designed such that it's possible to emit ever-changing values for each
* [UserAction] to enable, disable, or change the destination scene of a given user action.
*/
- fun destinationScenes(containerName: String): StateFlow<Map<UserAction, SceneModel>> =
+ fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
index 0327edb..8204edc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -16,10 +16,8 @@
package com.android.systemui.scene.shared.model
-/** Models the configuration of a single scene container. */
+/** Models the configuration of the scene container. */
data class SceneContainerConfig(
- /** Container name. Must be unique across all containers in System UI. */
- val name: String,
/**
* The keys to all scenes in the container, sorted by z-order such that the last one renders on
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
index 7562a5a..f74005b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
@@ -16,44 +16,27 @@
package com.android.systemui.scene.shared.model
-import com.android.systemui.dagger.SysUISingleton
import dagger.Module
import dagger.Provides
-import javax.inject.Named
@Module
object SceneContainerConfigModule {
@Provides
- fun containerConfigs(): Map<String, SceneContainerConfig> {
- return mapOf(
- SceneContainerNames.SYSTEM_UI_DEFAULT to
- SceneContainerConfig(
- name = SceneContainerNames.SYSTEM_UI_DEFAULT,
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last
- // one is top-most.
- sceneKeys =
- listOf(
- SceneKey.Gone,
- SceneKey.Lockscreen,
- SceneKey.Bouncer,
- SceneKey.Shade,
- SceneKey.QuickSettings,
- ),
- initialSceneKey = SceneKey.Lockscreen,
+ fun containerConfig(): SceneContainerConfig {
+ return SceneContainerConfig(
+ // Note that this list is in z-order. The first one is the bottom-most and the
+ // last
+ // one is top-most.
+ sceneKeys =
+ listOf(
+ SceneKey.Gone,
+ SceneKey.Lockscreen,
+ SceneKey.Bouncer,
+ SceneKey.Shade,
+ SceneKey.QuickSettings,
),
+ initialSceneKey = SceneKey.Lockscreen,
)
}
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun provideDefaultSceneContainerConfig(
- configs: Map<String, SceneContainerConfig>,
- ): SceneContainerConfig {
- return checkNotNull(configs[SceneContainerNames.SYSTEM_UI_DEFAULT]) {
- "No SceneContainerConfig named \"${SceneContainerNames.SYSTEM_UI_DEFAULT}\"."
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 005f48d9..f44748a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -17,16 +17,20 @@
package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
-/** Models UI state for a single scene container. */
-class SceneContainerViewModel(
+/** Models UI state for the scene container. */
+@SysUISingleton
+class SceneContainerViewModel
+@Inject
+constructor(
private val interactor: SceneInteractor,
- val containerName: String,
) {
/** A flow of motion events originating from outside of the scene framework. */
val remoteUserInput: StateFlow<RemoteUserInput?> = interactor.remoteUserInput
@@ -37,22 +41,22 @@
* The scenes will be sorted in z-order such that the last one is the one that should be
* rendered on top of all previous ones.
*/
- val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName)
+ val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
/** The current scene. */
- val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName)
+ val currentScene: StateFlow<SceneModel> = interactor.currentScene
/** Whether the container is visible. */
- val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName)
+ val isVisible: StateFlow<Boolean> = interactor.isVisible
/** Requests a transition to the scene with the given key. */
fun setCurrentScene(scene: SceneModel) {
- interactor.setCurrentScene(containerName, scene)
+ interactor.setCurrentScene(scene)
}
/** Notifies of the progress of a scene transition. */
fun setSceneTransitionProgress(progress: Float) {
- interactor.setSceneTransitionProgress(containerName, progress)
+ interactor.setSceneTransitionProgress(progress)
}
/** Handles a [MotionEvent] representing remote user input. */
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
deleted file mode 100644
index 100f427..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelModule.kt
+++ /dev/null
@@ -1,40 +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.scene.ui.viewmodel
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneContainerNames
-import dagger.Module
-import dagger.Provides
-import javax.inject.Named
-
-@Module
-object SceneContainerViewModelModule {
-
- @Provides
- @SysUISingleton
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
- fun defaultSceneContainerViewModel(
- interactor: SceneInteractor,
- ): SceneContainerViewModel {
- return SceneContainerViewModel(
- interactor = interactor,
- containerName = SceneContainerNames.SYSTEM_UI_DEFAULT,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 773f35e..2ea63c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -140,6 +140,7 @@
import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
import com.android.systemui.keyguard.ui.view.KeyguardRootView;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
@@ -597,6 +598,9 @@
private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
private final GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
+ private final GoneToDreamingLockscreenHostedTransitionViewModel
+ mGoneToDreamingLockscreenHostedTransitionViewModel;
+
private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -605,6 +609,7 @@
private final CoroutineDispatcher mMainDispatcher;
private boolean mIsAnyMultiShadeExpanded;
private boolean mIsOcclusionTransitionRunning = false;
+ private boolean mIsGoneToDreamingLockscreenHostedTransitionRunning;
private int mDreamingToLockscreenTransitionTranslationY;
private int mOccludedToLockscreenTransitionTranslationY;
private int mLockscreenToDreamingTransitionTranslationY;
@@ -652,6 +657,25 @@
step.getTransitionState() == TransitionState.RUNNING;
};
+ private final Consumer<TransitionStep> mGoneToDreamingLockscreenHostedTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ mIsGoneToDreamingLockscreenHostedTransitionRunning = mIsOcclusionTransitionRunning;
+ };
+
+ private final Consumer<TransitionStep> mLockscreenToDreamingLockscreenHostedTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
+ private final Consumer<TransitionStep> mDreamingLockscreenHostedToLockscreenTransition =
+ (TransitionStep step) -> {
+ mIsOcclusionTransitionRunning =
+ step.getTransitionState() == TransitionState.RUNNING;
+ };
+
private final Consumer<TransitionStep> mLockscreenToOccludedTransition =
(TransitionStep step) -> {
mIsOcclusionTransitionRunning =
@@ -734,6 +758,8 @@
OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel,
+ GoneToDreamingLockscreenHostedTransitionViewModel
+ goneToDreamingLockscreenHostedTransitionViewModel,
LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -761,6 +787,8 @@
mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
+ mGoneToDreamingLockscreenHostedTransitionViewModel =
+ goneToDreamingLockscreenHostedTransitionViewModel;
mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mKeyguardInteractor = keyguardInteractor;
@@ -1091,6 +1119,24 @@
mDreamingToLockscreenTransitionTranslationY),
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
+ // Gone -> Dreaming hosted in lockscreen
+ collectFlow(mView, mKeyguardTransitionInteractor
+ .getGoneToDreamingLockscreenHostedTransition(),
+ mGoneToDreamingLockscreenHostedTransition, mMainDispatcher);
+ collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(),
+ setTransitionAlpha(mNotificationStackScrollLayoutController),
+ mMainDispatcher);
+
+ // Lockscreen -> Dreaming hosted in lockscreen
+ collectFlow(mView, mKeyguardTransitionInteractor
+ .getLockscreenToDreamingLockscreenHostedTransition(),
+ mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher);
+
+ // Dreaming hosted in lockscreen -> Lockscreen
+ collectFlow(mView, mKeyguardTransitionInteractor
+ .getDreamingLockscreenHostedToLockscreenTransition(),
+ mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher);
+
// Occluded->Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
mOccludedToLockscreenTransition, mMainDispatcher);
@@ -1496,13 +1542,18 @@
mAnimateNextPositionUpdate = false;
}
+ private boolean shouldAnimateKeyguardStatusViewAlignment() {
+ // Do not animate when transitioning from Gone->DreamingLockscreenHosted
+ return !mIsGoneToDreamingLockscreenHostedTransitionRunning;
+ }
+
private void updateClockAppearance() {
int userSwitcherPreferredY = mStatusBarHeaderHeightKeyguard;
boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
mKeyguardStatusViewController.displayClock(computeDesiredClockSize(),
shouldAnimateClockChange);
- updateKeyguardStatusViewAlignment(/* animate= */true);
+ updateKeyguardStatusViewAlignment(/* animate= */shouldAnimateKeyguardStatusViewAlignment());
int userSwitcherHeight = mKeyguardQsUserSwitchController != null
? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
if (mKeyguardUserSwitcherController != null) {
@@ -1625,6 +1676,10 @@
// overlap.
return true;
}
+ if (isActiveDreamLockscreenHosted()) {
+ // Dreaming hosted in lockscreen, no "visible" notifications. Safe to center the clock.
+ return true;
+ }
if (mNotificationListContainer.hasPulsingNotifications()) {
// Pulsing notification appears on the right. Move clock left to avoid overlap.
return false;
@@ -1653,6 +1708,11 @@
return mDozing && mDozeParameters.getAlwaysOn();
}
+
+ private boolean isActiveDreamLockscreenHosted() {
+ return mKeyguardInteractor.isActiveDreamLockscreenHosted().getValue();
+ }
+
private boolean hasVisibleNotifications() {
return mNotificationStackScrollLayoutController
.getVisibleNotificationCount() != 0
@@ -2820,7 +2880,9 @@
@Override
public void onScreenTurningOn() {
- mKeyguardStatusViewController.dozeTimeTick();
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ mKeyguardStatusViewController.dozeTimeTick();
+ }
}
private void onMiddleClicked() {
@@ -3070,10 +3132,11 @@
}
}
- @Override
public void dozeTimeTick() {
mLockIconViewController.dozeTimeTick();
- mKeyguardStatusViewController.dozeTimeTick();
+ if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+ mKeyguardStatusViewController.dozeTimeTick();
+ }
if (mInterpolatedDarkAmount > 0) {
positionClockAndNotifications();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index e02c427..2955118 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -36,7 +36,6 @@
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.ui.view.SceneWindowRootView
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -73,11 +72,8 @@
fun providesWindowRootView(
layoutInflater: LayoutInflater,
featureFlags: FeatureFlags,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
viewModelProvider: Provider<SceneContainerViewModel>,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
containerConfigProvider: Provider<SceneContainerConfig>,
- @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
layoutInsetController: NotificationInsetsController,
): WindowRootView {
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 8a96a47..0b3ed56 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
@@ -16,12 +16,11 @@
package com.android.systemui.shade.ui.viewmodel
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -29,32 +28,29 @@
import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the shade scene. */
+@SysUISingleton
class ShadeSceneViewModel
-@AssistedInject
+@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
- @Assisted private val containerName: String,
+ private val lockscreenSceneInteractor: LockscreenSceneInteractor,
) {
- private val lockScreenInteractor: LockscreenSceneInteractor =
- lockscreenSceneInteractorFactory.create(containerName)
-
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
- lockScreenInteractor.isDeviceLocked
+ lockscreenSceneInteractor.isDeviceLocked
.map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
initialValue =
upDestinationSceneKey(
- isLocked = lockScreenInteractor.isDeviceLocked.value,
+ isLocked = lockscreenSceneInteractor.isDeviceLocked.value,
),
)
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() {
- lockScreenInteractor.dismissLockscreen()
+ lockscreenSceneInteractor.dismissLockscreen()
}
private fun upDestinationSceneKey(
@@ -62,11 +58,4 @@
): SceneKey {
return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
}
-
- @AssistedFactory
- interface Factory {
- fun create(
- containerName: String,
- ): ShadeSceneViewModel
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt
new file mode 100644
index 0000000..92e9765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationMemoryModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.notification.dagger
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface NotificationMemoryModule {
+
+ /** Binds memory monitor into startable map. */
+ @Binds
+ @IntoMap
+ @ClassKey(NotificationMemoryMonitor::class)
+ fun bindsNotificationMemoryMonitorStartable(monitor: NotificationMemoryMonitor): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 31d4ab9..ea3a8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -91,6 +91,7 @@
NotificationSectionHeadersModule.class,
NotificationListViewModelModule.class,
ActivatableNotificationViewModelModule.class,
+ NotificationMemoryModule.class,
})
public interface NotificationsModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index f7bd177..106d11f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -39,7 +39,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.logging.NotificationLogger
-import com.android.systemui.statusbar.notification.logging.NotificationMemoryMonitor
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.wm.shell.bubbles.Bubbles
@@ -72,7 +71,6 @@
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
private val fgsNotifListener: ForegroundServiceNotificationListener,
- private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
private val featureFlags: FeatureFlags
) : NotificationsController {
@@ -108,7 +106,6 @@
notificationLogger.setUpWithContainer(listContainer)
peopleSpaceWidgetManager.attach(notificationListener)
fgsNotifListener.init()
- memoryMonitor.get().init()
}
// TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index f38c1e5..0fdf681 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.logging
import android.util.Log
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -32,13 +33,13 @@
private val featureFlags: FeatureFlags,
private val notificationMemoryDumper: NotificationMemoryDumper,
private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
-) {
+) : CoreStartable {
companion object {
private const val TAG = "NotificationMemory"
}
- fun init() {
+ override fun start() {
Log.d(TAG, "NotificationMemoryMonitor initialized.")
notificationMemoryDumper.init()
if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
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 36a8e98..5e7e4be 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
@@ -426,16 +426,21 @@
updateAppearAnimationAlpha();
updateAppearRect();
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mWasCancelled;
+ private boolean mRunWithoutInterruptions;
@Override
public void onAnimationEnd(Animator animation) {
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
- if (!mWasCancelled) {
+ if (mRunWithoutInterruptions) {
enableAppearDrawing(false);
- onAppearAnimationFinished(isAppearing);
+ }
+
+ // We need to reset the View state, even if the animation was cancelled
+ onAppearAnimationFinished(isAppearing);
+
+ if (mRunWithoutInterruptions) {
InteractionJankMonitor.getInstance().end(getCujType(isAppearing));
} else {
InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing));
@@ -444,7 +449,7 @@
@Override
public void onAnimationStart(Animator animation) {
- mWasCancelled = false;
+ mRunWithoutInterruptions = true;
Configuration.Builder builder = Configuration.Builder
.withView(getCujType(isAppearing), ActivatableNotificationView.this);
InteractionJankMonitor.getInstance().begin(builder);
@@ -452,7 +457,7 @@
@Override
public void onAnimationCancel(Animator animation) {
- mWasCancelled = true;
+ mRunWithoutInterruptions = false;
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 035bdf1..f6ccaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -471,7 +471,7 @@
};
final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
- && !mStatusBarStateController.isDozing();
+ && mPowerManager.isInteractive();
if (mMode != MODE_NONE && !wakingFromDream) {
wakeUp.run();
@@ -509,7 +509,7 @@
// later to awaken.
}
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
- mKeyguardViewMediator.onWakeAndUnlocking();
+ mKeyguardViewMediator.onWakeAndUnlocking(wakingFromDream);
Trace.endSection();
break;
case MODE_ONLY_WAKE:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ed9722e..801cdbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -38,7 +38,6 @@
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
@@ -94,7 +93,6 @@
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final AuthController mAuthController;
private final NotificationIconAreaController mNotificationIconAreaController;
- private final BurnInInteractor mBurnInInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private ShadeViewController mNotificationPanel;
private View mAmbientIndicationContainer;
@@ -118,8 +116,7 @@
NotificationWakeUpCoordinator notificationWakeUpCoordinator,
AuthController authController,
NotificationIconAreaController notificationIconAreaController,
- DozeInteractor dozeInteractor,
- BurnInInteractor burnInInteractor) {
+ DozeInteractor dozeInteractor) {
super();
mDozeLog = dozeLog;
mPowerManager = powerManager;
@@ -138,7 +135,6 @@
mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
mAuthController = authController;
mNotificationIconAreaController = notificationIconAreaController;
- mBurnInInteractor = burnInInteractor;
mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
mDozeInteractor = dozeInteractor;
}
@@ -317,7 +313,6 @@
if (mAmbientIndicationContainer instanceof DozeReceiver) {
((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
}
- mBurnInInteractor.dozeTimeTick();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 8c3050d..1227287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,7 +30,6 @@
import com.android.systemui.flags.Flags
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
@@ -183,7 +182,7 @@
sceneInteractor.get()
.onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
// TODO(b/291965119): remove once view is expanded to cover the status bar
- sceneInteractor.get().setVisible(SceneContainerNames.SYSTEM_UI_DEFAULT, true)
+ sceneInteractor.get().setVisible(true)
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 68a6b3d..fa9b9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,7 +37,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneContainerNames;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -126,7 +125,7 @@
if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
javaAdapter.get().alwaysCollectFlow(
- sceneInteractor.get().isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT),
+ sceneInteractor.get().isVisible(),
this::onShadeExpansionFullyChanged);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 1c3a8850..dabdcc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -46,7 +46,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.Utils;
import com.android.systemui.util.settings.GlobalSettings;
@@ -68,17 +67,17 @@
private final Context mContext;
private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final SettingObserver mModeSetting;
- private final SettingObserver mConfigSetting;
private final NotificationManager mNoMan;
private final AlarmManager mAlarmManager;
private final SetupObserver mSetupObserver;
private final UserManager mUserManager;
+ private final GlobalSettings mGlobalSettings;
private int mUserId;
private boolean mRegistered;
private ZenModeConfig mConfig;
- private int mZenMode;
+ // This value is changed in the main thread, but may be read in a background thread.
+ private volatile int mZenMode;
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
@@ -111,18 +110,20 @@
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
- mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
- userTracker.getUserId()) {
+ mGlobalSettings = globalSettings;
+
+ ContentObserver modeContentObserver = new ContentObserver(handler) {
@Override
- protected void handleValueChanged(int value, boolean observedChange) {
+ public void onChange(boolean selfChange) {
+ int value = getModeSettingValueFromProvider();
+ Log.d(TAG, "Zen mode setting changed to " + value);
updateZenMode(value);
fireZenChanged(value);
}
};
- mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
- userTracker.getUserId()) {
+ ContentObserver configContentObserver = new ContentObserver(handler) {
@Override
- protected void handleValueChanged(int value, boolean observedChange) {
+ public void onChange(boolean selfChange) {
try {
Trace.beginSection("updateZenModeConfig");
updateZenModeConfig();
@@ -132,9 +133,9 @@
}
};
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- mModeSetting.setListening(true);
- updateZenMode(mModeSetting.getValue());
- mConfigSetting.setListening(true);
+ globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ updateZenMode(getModeSettingValueFromProvider());
+ globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver);
updateZenModeConfig();
updateConsolidatedNotificationPolicy();
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -146,6 +147,10 @@
dumpManager.registerDumpable(getClass().getSimpleName(), this);
}
+ private int getModeSettingValueFromProvider() {
+ return mGlobalSettings.getInt(Global.ZEN_MODE, /* default */ Global.ZEN_MODE_OFF);
+ }
+
@Override
public boolean isVolumeRestricted() {
return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index 06cc96e..d696979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -46,6 +46,7 @@
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
+ setClipChildren(false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index eef66b6..a983d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -22,83 +22,86 @@
class DynamicColors {
companion object {
- private val MDC = MaterialDynamicColors()
- @JvmField
- val ALL_DYNAMIC_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
- arrayListOf(
- Pair.create("primary_container", MDC.primaryContainer()),
- Pair.create("on_primary_container", MDC.onPrimaryContainer()),
- Pair.create("primary", MDC.primary()),
- Pair.create("on_primary", MDC.onPrimary()),
- Pair.create("secondary_container", MDC.secondaryContainer()),
- Pair.create("on_secondary_container", MDC.onSecondaryContainer()),
- Pair.create("secondary", MDC.secondary()),
- Pair.create("on_secondary", MDC.onSecondary()),
- Pair.create("tertiary_container", MDC.tertiaryContainer()),
- Pair.create("on_tertiary_container", MDC.onTertiaryContainer()),
- Pair.create("tertiary", MDC.tertiary()),
- Pair.create("on_tertiary", MDC.onTertiary()),
- Pair.create("background", MDC.background()),
- Pair.create("on_background", MDC.onBackground()),
- Pair.create("surface", MDC.surface()),
- Pair.create("on_surface", MDC.onSurface()),
- Pair.create("surface_container_low", MDC.surfaceContainerLow()),
- Pair.create("surface_container_lowest", MDC.surfaceContainerLowest()),
- Pair.create("surface_container", MDC.surfaceContainer()),
- Pair.create("surface_container_high", MDC.surfaceContainerHigh()),
- Pair.create("surface_container_highest", MDC.surfaceContainerHighest()),
- Pair.create("surface_bright", MDC.surfaceBright()),
- Pair.create("surface_dim", MDC.surfaceDim()),
- Pair.create("surface_variant", MDC.surfaceVariant()),
- Pair.create("on_surface_variant", MDC.onSurfaceVariant()),
- Pair.create("outline", MDC.outline()),
- Pair.create("outline_variant", MDC.outlineVariant()),
- Pair.create("error", MDC.error()),
- Pair.create("on_error", MDC.onError()),
- Pair.create("error_container", MDC.errorContainer()),
- Pair.create("on_error_container", MDC.onErrorContainer()),
- Pair.create("control_activated", MDC.controlActivated()),
- Pair.create("control_normal", MDC.controlNormal()),
- Pair.create("control_highlight", MDC.controlHighlight()),
- Pair.create("text_primary_inverse", MDC.textPrimaryInverse()),
+ @JvmStatic
+ fun allDynamicColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+ val mdc = MaterialDynamicColors(isExtendedFidelity)
+ return arrayListOf(
+ Pair.create("primary_container", mdc.primaryContainer()),
+ Pair.create("on_primary_container", mdc.onPrimaryContainer()),
+ Pair.create("primary", mdc.primary()),
+ Pair.create("on_primary", mdc.onPrimary()),
+ Pair.create("secondary_container", mdc.secondaryContainer()),
+ Pair.create("on_secondary_container", mdc.onSecondaryContainer()),
+ Pair.create("secondary", mdc.secondary()),
+ Pair.create("on_secondary", mdc.onSecondary()),
+ Pair.create("tertiary_container", mdc.tertiaryContainer()),
+ Pair.create("on_tertiary_container", mdc.onTertiaryContainer()),
+ Pair.create("tertiary", mdc.tertiary()),
+ Pair.create("on_tertiary", mdc.onTertiary()),
+ Pair.create("background", mdc.background()),
+ Pair.create("on_background", mdc.onBackground()),
+ Pair.create("surface", mdc.surface()),
+ Pair.create("on_surface", mdc.onSurface()),
+ Pair.create("surface_container_low", mdc.surfaceContainerLow()),
+ Pair.create("surface_container_lowest", mdc.surfaceContainerLowest()),
+ Pair.create("surface_container", mdc.surfaceContainer()),
+ Pair.create("surface_container_high", mdc.surfaceContainerHigh()),
+ Pair.create("surface_container_highest", mdc.surfaceContainerHighest()),
+ Pair.create("surface_bright", mdc.surfaceBright()),
+ Pair.create("surface_dim", mdc.surfaceDim()),
+ Pair.create("surface_variant", mdc.surfaceVariant()),
+ Pair.create("on_surface_variant", mdc.onSurfaceVariant()),
+ Pair.create("outline", mdc.outline()),
+ Pair.create("outline_variant", mdc.outlineVariant()),
+ Pair.create("error", mdc.error()),
+ Pair.create("on_error", mdc.onError()),
+ Pair.create("error_container", mdc.errorContainer()),
+ Pair.create("on_error_container", mdc.onErrorContainer()),
+ Pair.create("control_activated", mdc.controlActivated()),
+ Pair.create("control_normal", mdc.controlNormal()),
+ Pair.create("control_highlight", mdc.controlHighlight()),
+ Pair.create("text_primary_inverse", mdc.textPrimaryInverse()),
Pair.create(
"text_secondary_and_tertiary_inverse",
- MDC.textSecondaryAndTertiaryInverse()
+ mdc.textSecondaryAndTertiaryInverse()
),
Pair.create(
"text_primary_inverse_disable_only",
- MDC.textPrimaryInverseDisableOnly()
+ mdc.textPrimaryInverseDisableOnly()
),
Pair.create(
"text_secondary_and_tertiary_inverse_disabled",
- MDC.textSecondaryAndTertiaryInverseDisabled()
+ mdc.textSecondaryAndTertiaryInverseDisabled()
),
- Pair.create("text_hint_inverse", MDC.textHintInverse()),
- Pair.create("palette_key_color_primary", MDC.primaryPaletteKeyColor()),
- Pair.create("palette_key_color_secondary", MDC.secondaryPaletteKeyColor()),
- Pair.create("palette_key_color_tertiary", MDC.tertiaryPaletteKeyColor()),
- Pair.create("palette_key_color_neutral", MDC.neutralPaletteKeyColor()),
+ Pair.create("text_hint_inverse", mdc.textHintInverse()),
+ Pair.create("palette_key_color_primary", mdc.primaryPaletteKeyColor()),
+ Pair.create("palette_key_color_secondary", mdc.secondaryPaletteKeyColor()),
+ Pair.create("palette_key_color_tertiary", mdc.tertiaryPaletteKeyColor()),
+ Pair.create("palette_key_color_neutral", mdc.neutralPaletteKeyColor()),
Pair.create(
"palette_key_color_neutral_variant",
- MDC.neutralVariantPaletteKeyColor()
+ mdc.neutralVariantPaletteKeyColor()
),
)
+ }
- @JvmField
- val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
- arrayListOf(
- Pair.create("primary_fixed", MDC.primaryFixed()),
- Pair.create("primary_fixed_dim", MDC.primaryFixedDim()),
- Pair.create("on_primary_fixed", MDC.onPrimaryFixed()),
- Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()),
- Pair.create("secondary_fixed", MDC.secondaryFixed()),
- Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()),
- Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()),
- Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()),
- Pair.create("tertiary_fixed", MDC.tertiaryFixed()),
- Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()),
- Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()),
- Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()),
+ @JvmStatic
+ fun getFixedColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> {
+ val mdc = MaterialDynamicColors(isExtendedFidelity)
+ return arrayListOf(
+ Pair.create("primary_fixed", mdc.primaryFixed()),
+ Pair.create("primary_fixed_dim", mdc.primaryFixedDim()),
+ Pair.create("on_primary_fixed", mdc.onPrimaryFixed()),
+ Pair.create("on_primary_fixed_variant", mdc.onPrimaryFixedVariant()),
+ Pair.create("secondary_fixed", mdc.secondaryFixed()),
+ Pair.create("secondary_fixed_dim", mdc.secondaryFixedDim()),
+ Pair.create("on_secondary_fixed", mdc.onSecondaryFixed()),
+ Pair.create("on_secondary_fixed_variant", mdc.onSecondaryFixedVariant()),
+ Pair.create("tertiary_fixed", mdc.tertiaryFixed()),
+ Pair.create("tertiary_fixed_dim", mdc.tertiaryFixedDim()),
+ Pair.create("on_tertiary_fixed", mdc.onTertiaryFixed()),
+ Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()),
)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 06f68c6..5a9f5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -130,6 +130,7 @@
private final boolean mIsMonochromaticEnabled;
private final Context mContext;
private final boolean mIsMonetEnabled;
+ private final boolean mIsFidelityEnabled;
private final UserTracker mUserTracker;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Resources mResources;
@@ -398,6 +399,7 @@
mContext = context;
mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
+ mIsFidelityEnabled = featureFlags.isEnabled(Flags.COLOR_FIDELITY);
mDeviceProvisionedController = deviceProvisionedController;
mBroadcastDispatcher = broadcastDispatcher;
mUserManager = userManager;
@@ -632,7 +634,7 @@
private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) {
String suffix = isDark ? "dark" : "light";
DynamicScheme scheme = isDark ? mDynamicSchemeDark : mDynamicSchemeLight;
- DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.forEach(p -> {
+ DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> {
String resourceName = "android:color/system_" + p.first + "_" + suffix;
int colorValue = p.second.getArgb(scheme);
overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
@@ -641,7 +643,7 @@
}
private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
- DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> {
+ DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> {
String resourceName = "android:color/system_" + p.first;
int colorValue = p.second.getArgb(mDynamicSchemeLight);
overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
@@ -660,7 +662,7 @@
Resources res = userHandle.isSystem()
? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
Resources.Theme theme = mContext.getTheme();
- MaterialDynamicColors dynamicColors = new MaterialDynamicColors();
+ MaterialDynamicColors dynamicColors = new MaterialDynamicColors(mIsFidelityEnabled);
if (!(res.getColor(android.R.color.system_accent1_500, theme)
== mColorScheme.getAccent1().getS500()
&& res.getColor(android.R.color.system_accent2_500, theme)
@@ -819,6 +821,7 @@
pw.println("mNeutralOverlay=" + mNeutralOverlay);
pw.println("mDynamicOverlay=" + mDynamicOverlay);
pw.println("mIsMonetEnabled=" + mIsMonetEnabled);
+ pw.println("mIsFidelityEnabled=" + mIsFidelityEnabled);
pw.println("mColorScheme=" + mColorScheme);
pw.println("mNeedsOverlayCreation=" + mNeedsOverlayCreation);
pw.println("mAcceptColorEvents=" + mAcceptColorEvents);
diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
index 757b4e5..13c1019 100644
--- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java
@@ -25,16 +25,12 @@
import androidx.concurrent.futures.CallbackToFutureAdapter;
-import com.android.systemui.dagger.qualifiers.Main;
-
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
-
/**
* {@link TouchInsetManager} handles setting the touchable inset regions for a given View. This
* is useful for passing through touch events for all but select areas.
@@ -153,8 +149,7 @@
* Default constructor.
* @param executor An {@link Executor} to marshal all operations on.
*/
- @Inject
- public TouchInsetManager(@Main Executor executor) {
+ public TouchInsetManager(Executor executor) {
mExecutor = executor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e73cb3..1e65566 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -79,15 +79,15 @@
private val hapticsScale: Float
get() {
- val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
- return intensityString.toFloatOrNull() ?: 0.1f
+ val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.5")
+ return intensityString.toFloatOrNull() ?: 0.5f
}
private val hapticsScaleTick: Float
get() {
val intensityString =
- SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
- return intensityString.toFloatOrNull() ?: 0.6f
+ SystemProperties.get("persist.unfold.haptics_scale_end_tick", "1.0")
+ return intensityString.toFloatOrNull() ?: 1.0f
}
private val primitivesCount: Int
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d39a53d..53f4837 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -44,6 +44,7 @@
import android.media.session.MediaSession.Token;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -57,6 +58,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
+import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.GuardedBy;
@@ -65,6 +67,7 @@
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.VolumeDialogController;
@@ -131,7 +134,7 @@
private final Receiver mReceiver = new Receiver();
private final RingerModeObservers mRingerModeObservers;
private final MediaSessions mMediaSessions;
- private final CaptioningManager mCaptioningManager;
+ private CaptioningManager mCaptioningManager;
private final KeyguardManager mKeyguardManager;
private final ActivityManager mActivityManager;
private final UserTracker mUserTracker;
@@ -179,11 +182,11 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ @Main Handler mainHandler
) {
mContext = context.getApplicationContext();
mPackageManager = packageManager;
@@ -209,10 +212,12 @@
mVibrator = vibrator;
mHasVibrator = mVibrator.hasVibrator();
mAudioService = iAudioService;
- mCaptioningManager = captioningManager;
mKeyguardManager = keyguardManager;
mActivityManager = activityManager;
mUserTracker = userTracker;
+ mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mainHandler));
+ createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
+
dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -316,6 +321,25 @@
mWorker.sendEmptyMessage(W.GET_STATE);
}
+ /**
+ * We met issues about the wrong state of System Caption in multi-user mode.
+ * It happened in the usage of CaptioningManager Service from SysUI process
+ * that is a global system process of User 0.
+ * Therefore, we have to add callback on UserTracker that allows us to get the Context of
+ * active User and then get the corresponding CaptioningManager Service for further usages.
+ */
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ createCaptioningManagerServiceByUserContext(userContext);
+ }
+ };
+
+ private void createCaptioningManagerServiceByUserContext(@NonNull Context userContext) {
+ mCaptioningManager = userContext.getSystemService(CaptioningManager.class);
+ }
+
public boolean areCaptionsEnabled() {
return mCaptioningManager.isSystemAudioCaptioningEnabled();
}
@@ -719,7 +743,7 @@
* This method will never be called if the CSD (Computed Sound Dose) feature is
* not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
* the feature.
- * @param warning the type of warning to display, values are one of
+ * @param csdWarning the type of warning to display, values are one of
* {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
* {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
* {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 3abae6b..e447c29 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -733,29 +733,20 @@
// is
// not enough to trigger a dismissal of the keyguard.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Gone, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
clearInvocations(viewMediatorCallback)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -763,18 +754,12 @@
// scene
// does not dismiss the keyguard while we're not listening.
underTest.onViewDetached()
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Gone, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
// While not listening, moving back to the bouncer does not dismiss the keyguard.
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
runCurrent()
verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
@@ -782,10 +767,7 @@
// gone
// scene now does dismiss the keyguard again.
underTest.onViewAttached()
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Gone, null)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
runCurrent()
verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index cd18754..64e1458 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -138,6 +138,11 @@
@Test
public void asynchronouslyInflateView_setNeedsInput() {
+ when(mKeyguardSecurityViewControllerFactory.create(
+ any(), any(SecurityMode.class),
+ any(KeyguardSecurityCallback.class)))
+ .thenReturn(mKeyguardInputViewController);
+
ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor =
ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class);
mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
new file mode 100644
index 0000000..ba3dbf0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 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.keyguard;
+
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.jank.InteractionJankMonitor;
+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.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {
+
+ @Mock protected KeyguardStatusView mKeyguardStatusView;
+ @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
+ @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
+ @Mock protected KeyguardStateController mKeyguardStateController;
+ @Mock protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock protected ConfigurationController mConfigurationController;
+ @Mock protected DozeParameters mDozeParameters;
+ @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 DumpManager mDumpManager;
+ protected FakeKeyguardRepository mFakeKeyguardRepository;
+
+ protected KeyguardStatusViewController mController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ KeyguardInteractorFactory.WithDependencies deps = KeyguardInteractorFactory.create();
+ mFakeKeyguardRepository = deps.getRepository();
+
+ mController = new KeyguardStatusViewController(
+ mKeyguardStatusView,
+ mKeyguardSliceViewController,
+ mKeyguardClockSwitchController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mConfigurationController,
+ mDozeParameters,
+ mScreenOffAnimationController,
+ mKeyguardLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor,
+ deps.getKeyguardInteractor(),
+ mDumpManager) {
+ @Override
+ void setProperty(
+ AnimatableProperty property,
+ float value,
+ boolean animate) {
+ // Route into the mock version for verification
+ mControllerMock.setProperty(property, value, animate);
+ }
+ };
+
+ when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+ }
+
+ protected void givenViewAttached() {
+ ArgumentCaptor<View.OnAttachStateChangeListener> captor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ verify(mKeyguardStatusView, atLeast(1)).addOnAttachStateChangeListener(captor.capture());
+
+ for (View.OnAttachStateChangeListener listener : captor.getAllValues()) {
+ listener.onViewAttachedToWindow(mKeyguardStatusView);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 7114c22..20d9ef1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -23,80 +23,21 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
-import com.android.internal.jank.InteractionJankMonitor;
-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.plugins.ClockConfig;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner.class)
-public class KeyguardStatusViewControllerTest extends SysuiTestCase {
-
- @Mock private KeyguardStatusView mKeyguardStatusView;
- @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
- @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private ConfigurationController mConfigurationController;
- @Mock private DozeParameters mDozeParameters;
- @Mock private ScreenOffAnimationController mScreenOffAnimationController;
- @Mock private KeyguardLogger mKeyguardLogger;
- @Mock private KeyguardStatusViewController mControllerMock;
- @Mock private FeatureFlags mFeatureFlags;
- @Mock private InteractionJankMonitor mInteractionJankMonitor;
-
- @Mock private DumpManager mDumpManager;
-
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
-
- private KeyguardStatusViewController mController;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
-
- mController = new KeyguardStatusViewController(
- mKeyguardStatusView,
- mKeyguardSliceViewController,
- mKeyguardClockSwitchController,
- mKeyguardStateController,
- mKeyguardUpdateMonitor,
- mConfigurationController,
- mDozeParameters,
- mScreenOffAnimationController,
- mKeyguardLogger,
- mFeatureFlags,
- mInteractionJankMonitor,
- mDumpManager) {
- @Override
- void setProperty(
- AnimatableProperty property,
- float value,
- boolean animate) {
- // Route into the mock version for verification
- mControllerMock.setProperty(property, value, animate);
- }
- };
- }
+public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
@Test
public void dozeTimeTick_updatesSlice() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..2b9797e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.keyguard.shared.model.ScreenModel
+import com.android.systemui.keyguard.shared.model.ScreenState
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class KeyguardStatusViewControllerWithCoroutinesTest : KeyguardStatusViewControllerBaseTest() {
+
+ @Test
+ fun dozeTimeTickUpdatesSlices() = runTest {
+ mController.startCoroutines(coroutineContext)
+ givenViewAttached()
+ runCurrent()
+ clearInvocations(mKeyguardSliceViewController)
+
+ mFakeKeyguardRepository.dozeTimeTick()
+ runCurrent()
+ verify(mKeyguardSliceViewController).refresh()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun onScreenTurningOnUpdatesSlices() = runTest {
+ mController.startCoroutines(coroutineContext)
+ givenViewAttached()
+ runCurrent()
+ clearInvocations(mKeyguardSliceViewController)
+
+ mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_ON))
+ runCurrent()
+ verify(mKeyguardSliceViewController, never()).refresh()
+
+ // Should only be called during a 'turning on' event
+ mFakeKeyguardRepository.setScreenModel(ScreenModel(ScreenState.SCREEN_TURNING_ON))
+ runCurrent()
+ verify(mKeyguardSliceViewController).refresh()
+
+ coroutineContext.cancelChildren()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 5867a40c..44a2b68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -20,12 +20,13 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.animation.ObjectAnimator;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.core.animation.AnimatorTestRule;
+import androidx.core.animation.ObjectAnimator;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -37,6 +38,7 @@
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,6 +47,9 @@
@RunWithLooper
public class ExpandHelperTest extends SysuiTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private ExpandableNotificationRow mRow;
private ExpandHelper mExpandHelper;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 11ad206..a48fa5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -71,6 +71,7 @@
import android.testing.TestableLooper;
import android.testing.TestableResources;
import android.text.TextUtils;
+import android.util.Size;
import android.view.Display;
import android.view.IWindowSession;
import android.view.MotionEvent;
@@ -100,6 +101,7 @@
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -313,10 +315,10 @@
assertFalse(rects.isEmpty());
}
+ @Ignore("The default window size should be constrained after fixing b/288056772")
@Test
public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
- final int screenSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.magnification_max_frame_size) * 10;
+ final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
mInstrumentation.runOnMainSync(() -> {
@@ -543,17 +545,22 @@
}
@Test
- public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+ public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
// The default position is at the center of the screen.
final float expectedRatio = 0.5f;
- final Rect testWindowBounds = new Rect(
- mWindowManager.getCurrentWindowMetrics().getBounds());
- testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
- testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
+
+ // Screen size and density change
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ final Rect testWindowBounds = new Rect(
+ mWindowManager.getCurrentWindowMetrics().getBounds());
+ testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+ testWindowBounds.right + 100, testWindowBounds.bottom + 100);
mWindowManager.setWindowBounds(testWindowBounds);
mInstrumentation.runOnMainSync(() -> {
@@ -568,26 +575,49 @@
mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
0);
}
+
@Test
- public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
+ public void onScreenChangedToSavedDensity_enabled_restoreSavedMagnifierWindow() {
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+ int windowFrameSize = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+ mWindowMagnificationController.mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+ new Size(windowFrameSize, windowFrameSize));
+
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
Float.NaN);
});
- final int screenSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.magnification_max_frame_size) * 10;
+
+ WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+ assertTrue(params.width == windowFrameSize);
+ assertTrue(params.height == windowFrameSize);
+ }
+
+ @Test
+ public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+ mInstrumentation.runOnMainSync(() -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+ Float.NaN);
+ });
+ final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+ // Screen size and density change
+ mContext.getResources().getConfiguration().smallestScreenWidthDp =
+ mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
});
- final int halfScreenSize = screenSize / 2;
+ final int defaultWindowSize =
+ mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
+ WindowMagnificationSettings.MagnificationSize.MEDIUM);
WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
- // The frame size should be the half of smaller value of window height/width unless it
- //exceed the max frame size.
- assertTrue(params.width < halfScreenSize);
- assertTrue(params.height < halfScreenSize);
+
+ assertTrue(params.width == defaultWindowSize);
+ assertTrue(params.height == defaultWindowSize);
}
@Test
@@ -1136,6 +1166,11 @@
mWindowManager.setWindowInsets(testInsets);
}
+ private int updateMirrorSurfaceMarginDimension() {
+ return mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_mirror_surface_margin);
+ }
+
@Surface.Rotation
private int simulateRotateTheDevice() {
final Display display = Mockito.spy(mContext.getDisplay());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
new file mode 100644
index 0000000..04b0d70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Size;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WindowMagnificationSizePrefsTest extends SysuiTestCase {
+
+ WindowMagnificationSizePrefs mWindowMagnificationSizePrefs =
+ new WindowMagnificationSizePrefs(mContext);
+
+ @Test
+ public void saveSizeForCurrentDensity_getExpectedSize() {
+ Size testSize = new Size(500, 500);
+ mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+ assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity())
+ .isEqualTo(testSize);
+ }
+
+ @Test
+ public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
+ Size testSize = new Size(500, 500);
+ mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+ assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity())
+ .isTrue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 58982d1..7ab8e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -61,6 +62,7 @@
import android.os.VibrationAttributes;
import android.testing.TestableLooper.RunWithLooper;
import android.util.Pair;
+import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
@@ -1128,6 +1130,36 @@
}
@Test
+ public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled()
+ throws RemoteException {
+ when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ // WHEN ACTION_HOVER is received
+ verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+ MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+ mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
+ enterEvent.recycle();
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
+ mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
+ moveEvent.recycle();
+
+ // THEN context click haptic is played
+ verify(mVibrator).performHapticFeedback(
+ any(),
+ eq(HapticFeedbackConstants.CONTEXT_CLICK)
+ );
+ }
+
+ @Test
public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
// Configure UdfpsView to accept the ACTION_DOWN event
when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
@@ -1160,6 +1192,35 @@
}
@Test
+ public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled()
+ throws RemoteException {
+ when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ // WHEN ACTION_DOWN is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricExecutor.runAllReady();
+ downEvent.recycle();
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricExecutor.runAllReady();
+ moveEvent.recycle();
+
+ // THEN NO haptic played
+ verify(mVibrator, never()).performHapticFeedback(any(), anyInt());
+ }
+
+ @Test
public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
throws RemoteException {
// Disable new touch detection.
@@ -1514,4 +1575,45 @@
// THEN is fingerDown should be FALSE
assertFalse(mUdfpsController.isFingerDown());
}
+
+ @Test
+ public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate()
+ throws RemoteException {
+ // GIVEN UDFPS overlay is showing
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ // GIVEN there's been an AoD interrupt
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+ mScreenObserver.onScreenTurnedOn();
+ mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+ // THEN vibrate is used
+ verify(mVibrator).vibrate(
+ anyInt(),
+ anyString(),
+ eq(UdfpsController.EFFECT_CLICK),
+ eq("aod-lock-icon-longpress"),
+ eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES)
+ );
+ }
+
+ @Test
+ public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback()
+ throws RemoteException {
+ when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+ // GIVEN UDFPS overlay is showing
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ // GIVEN there's been an AoD interrupt
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+ mScreenObserver.onScreenTurnedOn();
+ mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+ // THEN vibrate is used
+ verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+ }
}
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 6babf04..14fc931 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
@@ -69,13 +69,12 @@
@Test
fun pinAuthMethod() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -101,14 +100,13 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setAutoConfirmEnabled(true)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
underTest.clearMessage()
@@ -138,13 +136,12 @@
@Test
fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.clearMessage()
@@ -168,14 +165,13 @@
@Test
fun passwordAuthMethod() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
@@ -201,14 +197,13 @@
@Test
fun patternAuthMethod() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
@@ -239,13 +234,12 @@
@Test
fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -253,12 +247,11 @@
@Test
fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
utils.authenticationRepository.setUnlocked(false)
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -266,8 +259,7 @@
@Test
fun showOrUnlockDevice_customMessageShown() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(underTest.message)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
@@ -275,7 +267,7 @@
utils.authenticationRepository.setUnlocked(false)
val customMessage = "Hello there!"
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
+ underTest.showOrUnlockDevice(customMessage)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
assertThat(message).isEqualTo(customMessage)
@@ -287,11 +279,10 @@
val isThrottled by collectLastValue(underTest.isThrottled)
val throttling by collectLastValue(underTest.throttling)
val message by collectLastValue(underTest.message)
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
runCurrent()
- underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+ underTest.showOrUnlockDevice()
runCurrent()
assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
assertThat(isThrottled).isFalse()
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 b1533fe..1f089ca 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
@@ -72,18 +72,14 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -96,18 +92,14 @@
@Test
fun onPasswordInputChanged() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -122,16 +114,12 @@
@Test
fun onAuthenticateKeyPressed_whenCorrect() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("password")
@@ -144,18 +132,14 @@
@Test
fun onAuthenticateKeyPressed_whenWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
@@ -170,18 +154,14 @@
@Test
fun onAuthenticateKeyPressed_correctAfterWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPasswordInputChanged("wrong")
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 f69cbb8..af54989 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
@@ -75,8 +75,7 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -84,10 +83,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -101,8 +97,7 @@
@Test
fun onDragStart() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -110,10 +105,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -129,18 +121,14 @@
@Test
fun onDragEnd_whenCorrect() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
utils.authenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -180,8 +168,7 @@
@Test
fun onDragEnd_whenWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -189,10 +176,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
@@ -216,8 +200,7 @@
@Test
fun onDragEnd_correctAfterWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val selectedDots by collectLastValue(underTest.selectedDots)
val currentDot by collectLastValue(underTest.currentDot)
@@ -225,10 +208,7 @@
AuthenticationMethodModel.Pattern
)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onDragStart()
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 8edc6cf..c12ed03 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
@@ -75,15 +75,11 @@
@Test
fun onShown() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -96,16 +92,12 @@
@Test
fun onPinButtonClicked() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -120,16 +112,12 @@
@Test
fun onBackspaceButtonClicked() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -146,15 +134,11 @@
@Test
fun onPinEdit() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
@@ -172,16 +156,12 @@
@Test
fun onBackspaceButtonLongPressed() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
runCurrent()
@@ -200,14 +180,10 @@
@Test
fun onAuthenticateButtonClicked_whenCorrect() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -222,16 +198,12 @@
@Test
fun onAuthenticateButtonClicked_whenWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -250,16 +222,12 @@
@Test
fun onAuthenticateButtonClicked_correctAfterWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
underTest.onPinButtonClicked(1)
@@ -286,15 +254,11 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -307,17 +271,13 @@
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAutoConfirmEnabled(true)
- sceneInteractor.setCurrentScene(
- SceneTestUtils.CONTAINER_1,
- SceneModel(SceneKey.Bouncer)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
underTest.onShown()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 38372a3..692d794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -24,7 +24,6 @@
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -36,7 +35,6 @@
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
@@ -44,6 +42,7 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -102,14 +101,11 @@
metricsLogger,
vibratorHelper,
controlsSettingsRepository,
- controlsSettingsDialogManager,
- featureFlags
))
coordinator.activityContext = mContext
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
- `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
@@ -155,13 +151,11 @@
coordinator.toggle(cvh, "", true)
verify(coordinator).bouncerOrRun(action)
- verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
verify(action).invoke()
}
@Test
fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
- `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
action = spy(coordinator.Action(ID, {}, false, false))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 71d2ec1..bd3d09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -16,8 +16,6 @@
import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
@@ -46,7 +44,6 @@
}
private val uiExecutor = FakeExecutor(FakeSystemClock())
- private val featureFlags = FakeFeatureFlags()
@Mock lateinit var controller: ControlsControllerImpl
@@ -65,7 +62,6 @@
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestableControlsEditingActivity(
- featureFlags,
uiExecutor,
controller,
userTracker,
@@ -81,8 +77,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
}
@Test
@@ -101,16 +95,7 @@
}
@Test
- fun testNewFlowDisabled_addControlsButton_gone() {
- with(launchActivity()) {
- val addControlsButton = requireViewById<Button>(R.id.addControls)
- assertThat(addControlsButton.visibility).isEqualTo(View.GONE)
- }
- }
-
- @Test
- fun testNewFlowEnabled_addControlsButton_visible() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ fun testAddControlsButton_visible() {
with(launchActivity()) {
val addControlsButton = requireViewById<Button>(R.id.addControls)
assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE)
@@ -120,7 +105,6 @@
@Test
fun testNotLaunchFromFavoriting_saveButton_disabled() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
with(launchActivity(isFromFavoriting = false)) {
val saveButton = requireViewById<Button>(R.id.done)
assertThat(saveButton.isEnabled).isFalse()
@@ -129,7 +113,6 @@
@Test
fun testLaunchFromFavoriting_saveButton_enabled() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
with(launchActivity(isFromFavoriting = true)) {
val saveButton = requireViewById<Button>(R.id.done)
assertThat(saveButton.isEnabled).isTrue()
@@ -138,7 +121,6 @@
@Test
fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
with(launchActivity(isFromFavoriting = false)) {
val addControls = requireViewById<Button>(R.id.addControls)
@@ -177,7 +159,6 @@
)
class TestableControlsEditingActivity(
- featureFlags: FakeFeatureFlags,
executor: FakeExecutor,
controller: ControlsControllerImpl,
userTracker: UserTracker,
@@ -186,7 +167,6 @@
private val latch: CountDownLatch
) :
ControlsEditingActivity(
- featureFlags,
executor,
controller,
userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f11c296..70d93a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -17,14 +17,11 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.SingleActivityFactory
import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.createLoadDataObject
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
@@ -73,8 +70,6 @@
@Mock lateinit var controller: ControlsControllerImpl
- @Mock lateinit var listingController: ControlsListingController
-
@Mock lateinit var userTracker: UserTracker
private var latch: CountDownLatch = CountDownLatch(1)
@@ -82,9 +77,6 @@
@Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
@Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
@Captor
- private lateinit var listingCallback:
- ArgumentCaptor<ControlsListingController.ControlsListingCallback>
- @Captor
private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>>
@Rule
@@ -93,10 +85,8 @@
ActivityTestRule(
/* activityFactory= */ SingleActivityFactory {
TestableControlsFavoritingActivity(
- featureFlags,
executor,
controller,
- listingController,
userTracker,
mockDispatcher,
latch
@@ -109,7 +99,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
}
// b/259549854 to root-cause and fix
@@ -130,14 +119,8 @@
}
@Test
- fun testNewFlowEnabled_buttons() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+ fun testButtons() {
with(launchActivity()) {
- verify(listingController).addCallback(listingCallback.capture())
- listingCallback.value.onServicesUpdated(
- listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
- )
-
val rearrangeButton = requireViewById<Button>(R.id.rearrange)
assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
assertThat(rearrangeButton.isEnabled).isFalse()
@@ -149,36 +132,8 @@
}
@Test
- fun testNewFlowDisabled_buttons() {
+ fun testRearrangePressed_savesAndlaunchesActivity() {
with(launchActivity()) {
- verify(listingController).addCallback(listingCallback.capture())
- activityRule.runOnUiThread {
- listingCallback.value.onServicesUpdated(
- listOf(
- mock(ControlsServiceInfo::class.java),
- mock(ControlsServiceInfo::class.java)
- )
- )
- }
-
- val rearrangeButton = requireViewById<Button>(R.id.rearrange)
- assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
- assertThat(rearrangeButton.isEnabled).isFalse()
-
- val otherAppsButton = requireViewById<Button>(R.id.other_apps)
- otherAppsButton.waitForPost()
- assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE)
- }
- }
-
- @Test
- fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() {
- featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
- with(launchActivity()) {
- verify(listingController).addCallback(capture(listingCallback))
- listingCallback.value.onServicesUpdated(
- listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
- )
verify(controller).loadForComponent(any(), capture(controlsCallback), any())
activityRule.runOnUiThread {
controlsCallback.value.accept(
@@ -224,19 +179,15 @@
)
class TestableControlsFavoritingActivity(
- featureFlags: FeatureFlags,
executor: Executor,
controller: ControlsControllerImpl,
- listingController: ControlsListingController,
userTracker: UserTracker,
private val mockDispatcher: OnBackInvokedDispatcher,
private val latch: CountDownLatch
) :
ControlsFavoritingActivity(
- featureFlags,
executor,
controller,
- listingController,
userTracker,
) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index ee213f7..b1061ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -37,7 +37,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.APP_PANELS_ALL_APPS_ALLOWED
-import com.android.systemui.flags.Flags.USE_APP_PANELS
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.ActivityTaskManagerProxy
import com.android.systemui.util.concurrency.FakeExecutor
@@ -124,8 +123,6 @@
arrayOf(componentName.packageName)
)
- // Return true by default, we'll test the false path
- `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(true)
// Return false by default, we'll test the true path
`when`(featureFlags.isEnabled(APP_PANELS_ALL_APPS_ALLOWED)).thenReturn(false)
@@ -445,34 +442,6 @@
}
@Test
- fun testActivityDefaultEnabled_flagDisabled_nullPanel() {
- `when`(featureFlags.isEnabled(USE_APP_PANELS)).thenReturn(false)
- val serviceInfo = ServiceInfo(
- componentName,
- activityName,
- )
-
- `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
- .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
-
- setUpQueryResult(listOf(
- ActivityInfo(
- activityName,
- enabled = true,
- exported = true,
- permission = Manifest.permission.BIND_CONTROLS
- )
- ))
-
- val list = listOf(serviceInfo)
- serviceListingCallbackCaptor.value.onServicesReloaded(list)
-
- executor.runAllReady()
-
- assertNull(controller.getCurrentServices()[0].panelActivity)
- }
-
- @Test
fun testActivityDifferentPackage_nullPanel() {
val serviceInfo = ServiceInfo(
componentName,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 1f66e5b..28328c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -316,7 +316,7 @@
TestableLooper.get(this).processAllMessages();
mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
- mViewMediator.onWakeAndUnlocking();
+ mViewMediator.onWakeAndUnlocking(false);
mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
TestableLooper.get(this).processAllMessages();
@@ -378,6 +378,8 @@
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
+ when(mPowerManager.isInteractive()).thenReturn(true);
+
// Given device is dreaming
when(mUpdateMonitor.isDreaming()).thenReturn(true);
@@ -717,14 +719,14 @@
@Test
public void testWakeAndUnlocking() {
- mViewMediator.onWakeAndUnlocking();
+ mViewMediator.onWakeAndUnlocking(false);
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
}
@Test
public void testWakeAndUnlockingOverDream() {
// Send signal to wake
- mViewMediator.onWakeAndUnlocking();
+ mViewMediator.onWakeAndUnlocking(true);
// Ensure not woken up yet
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -753,7 +755,7 @@
@Test
public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() {
// Send signal to wake
- mViewMediator.onWakeAndUnlocking();
+ mViewMediator.onWakeAndUnlocking(true);
// Ensure not woken up yet
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -783,6 +785,35 @@
}
@Test
+ public void testWakeAndUnlockingOverNonInteractiveDream_noWakeByKeyguardViewMediator() {
+ // Send signal to wake
+ mViewMediator.onWakeAndUnlocking(false);
+
+ // Ensure not woken up yet
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+ // Verify keyguard told of authentication
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+ mUpdateMonitor.getCurrentUser());
+ mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+ final ArgumentCaptor<Runnable> animationRunnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mStatusBarKeyguardViewManager).startPreHideAnimation(
+ animationRunnableCaptor.capture());
+
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ animationRunnableCaptor.getValue().run();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ mViewMediator.mViewMediatorCallback.keyguardGone();
+
+ // Verify not woken up.
+ verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+ }
+
+ @Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testDoKeyguardWhileInteractive_resets() {
mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 01a6c64..85ee0e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -541,10 +541,8 @@
}
@Test
- fun authenticateDoesNotRunIfUserIsCurrentlySwitching() =
- testScope.runTest {
- testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) }
- }
+ fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() =
+ testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } }
@Test
fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
@@ -840,7 +838,7 @@
@Test
fun detectDoesNotRunWhenUserSwitchingInProgress() =
- testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } }
+ testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } }
@Test
fun detectDoesNotRunWhenKeyguardGoingAway() =
@@ -1130,7 +1128,7 @@
.addLockoutResetCallback(faceLockoutResetCallback.capture())
biometricSettingsRepository.setFaceEnrolled(true)
biometricSettingsRepository.setIsFaceAuthEnabled(true)
- fakeUserRepository.setUserSwitching(false)
+ underTest.resumeFaceAuth()
trustRepository.setCurrentUserTrusted(false)
keyguardRepository.setKeyguardGoingAway(false)
keyguardRepository.setWakefulnessModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index ba7d349..5e3376a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -49,6 +49,7 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -85,13 +86,14 @@
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
+ private lateinit var systemClock: FakeSystemClock
private lateinit var underTest: KeyguardRepositoryImpl
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ systemClock = FakeSystemClock()
underTest =
KeyguardRepositoryImpl(
statusBarStateController,
@@ -107,6 +109,7 @@
dreamOverlayCallbackController,
mainDispatcher,
testScope.backgroundScope,
+ systemClock,
)
}
@@ -167,11 +170,15 @@
@Test
fun dozeTimeTick() =
testScope.runTest {
- var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
- underTest.dozeTimeTick()
- runCurrent()
+ val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
+ assertThat(lastDozeTimeTick).isEqualTo(0L)
- assertThat(dozeTimeTickValue()).isNull()
+ // WHEN dozeTimeTick updated
+ systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
+ underTest.dozeTimeTick()
+
+ // THEN listeners were updated to the latest uptime millis
+ assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index 069a486..6308269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -23,10 +23,9 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -48,7 +47,8 @@
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var systemClock: FakeSystemClock
+ private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var testScope: TestScope
private lateinit var underTest: BurnInInteractor
@@ -56,8 +56,10 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
configurationRepository = FakeConfigurationRepository()
- systemClock = FakeSystemClock()
-
+ KeyguardInteractorFactory.create().let {
+ keyguardInteractor = it.keyguardInteractor
+ fakeKeyguardRepository = it.repository
+ }
whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
setBurnInProgress(.65f)
@@ -68,26 +70,11 @@
burnInHelperWrapper,
testScope.backgroundScope,
configurationRepository,
- systemClock,
+ keyguardInteractor,
)
}
@Test
- fun dozeTimeTick_updatesOnDozeTimeTick() =
- testScope.runTest {
- // Initial state set to 0
- val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
- assertEquals(0L, lastDozeTimeTick)
-
- // WHEN dozeTimeTick updated
- incrementUptimeMillis()
- underTest.dozeTimeTick()
-
- // THEN listeners were updated to the latest uptime millis
- assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
- }
-
- @Test
fun udfpsBurnInOffset_updatesOnResolutionScaleChange() =
testScope.runTest {
val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset)
@@ -111,25 +98,18 @@
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.88f)
- incrementUptimeMillis()
- underTest.dozeTimeTick()
+ fakeKeyguardRepository.dozeTimeTick(10)
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.92f)
- incrementUptimeMillis()
- underTest.dozeTimeTick()
+ fakeKeyguardRepository.dozeTimeTick(20)
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
setBurnInProgress(.32f)
- incrementUptimeMillis()
- underTest.dozeTimeTick()
+ fakeKeyguardRepository.dozeTimeTick(30)
assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
}
- private fun incrementUptimeMillis() {
- systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
- }
-
private fun setBurnInProgress(progress: Float) {
burnInProgress = progress
whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 8636dd8..93f208e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -48,6 +48,7 @@
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,6 +75,7 @@
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+ private lateinit var fakeUserRepository: FakeUserRepository
private lateinit var fakeDeviceEntryFingerprintAuthRepository:
FakeDeviceEntryFingerprintAuthRepository
@@ -98,6 +100,7 @@
.keyguardTransitionInteractor
fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+ fakeUserRepository = FakeUserRepository()
underTest =
SystemUIKeyguardFaceAuthInteractor(
mContext,
@@ -131,7 +134,8 @@
featureFlags,
FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
keyguardUpdateMonitor,
- fakeDeviceEntryFingerprintAuthRepository
+ fakeDeviceEntryFingerprintAuthRepository,
+ fakeUserRepository,
)
}
@@ -212,6 +216,38 @@
}
@Test
+ fun faceAuthIsPausedWhenUserSwitchingIsInProgress() =
+ testScope.runTest {
+ underTest.start()
+
+ fakeUserRepository.setUserSwitching(false)
+ runCurrent()
+ fakeUserRepository.setUserSwitching(true)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
+ }
+
+ @Test
+ fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() =
+ testScope.runTest {
+ underTest.start()
+
+ // previously running
+ fakeUserRepository.setUserSwitching(true)
+ runCurrent()
+ fakeUserRepository.setUserSwitching(false)
+ runCurrent()
+
+ assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value!!.first)
+ .isEqualTo(FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING)
+ assertThat(faceAuthRepository.runningAuthRequest.value!!.second).isEqualTo(true)
+ }
+
+ @Test
fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
testScope.runTest {
underTest.start()
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 d01a46e..daf5ce6 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
@@ -89,6 +89,8 @@
FromAlternateBouncerTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
FromPrimaryBouncerTransitionInteractor
+ private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
+ FromDreamingLockscreenHostedTransitionInteractor
@Before
fun setUp() {
@@ -140,6 +142,15 @@
)
.apply { start() }
+ fromDreamingLockscreenHostedTransitionInteractor =
+ FromDreamingLockscreenHostedTransitionInteractor(
+ scope = testScope,
+ keyguardInteractor = createKeyguardInteractor(),
+ transitionRepository = transitionRepository,
+ transitionInteractor = transitionInteractor,
+ )
+ .apply { start() }
+
fromAodTransitionInteractor =
FromAodTransitionInteractor(
scope = testScope,
@@ -299,6 +310,38 @@
}
@Test
+ fun lockscreenToDreamingLockscreenHosted() =
+ testScope.runTest {
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runTransition(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+
+ // WHEN the device begins to dream and the dream is lockscreen hosted
+ keyguardRepository.setDreamingWithOverlay(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
+ assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToDozing() =
testScope.runTest {
// GIVEN a device with AOD not available
@@ -353,6 +396,149 @@
}
@Test
+ fun dreamingLockscreenHostedToLockscreen() =
+ testScope.runTest {
+ // GIVEN a device dreaming with the lockscreen hosted dream and not dozing
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+ runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+ // WHEN the lockscreen hosted dream stops
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to Lockscreen should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun dreamingLockscreenHostedToGone() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+ runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+ // WHEN biometrics succeeds with wake and unlock from dream mode
+ keyguardRepository.setBiometricUnlockState(
+ BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+ )
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to Gone should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun dreamingLockscreenHostedToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a device dreaming with lockscreen hosted dream and not dozing
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+ runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+ // WHEN the primary bouncer is set to show
+ bouncerRepository.setPrimaryShow(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to PRIMARY_BOUNCER should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun dreamingLockscreenHostedToDozing() =
+ testScope.runTest {
+ // GIVEN a device is dreaming with lockscreen hosted dream
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+ runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+ // WHEN the device begins to sleep
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.INITIALIZED, to = DozeStateModel.DOZE)
+ )
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to DOZING should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.to).isEqualTo(KeyguardState.DOZING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun dreamingLockscreenHostedToOccluded() =
+ testScope.runTest {
+ // GIVEN device is dreaming with lockscreen hosted dream and not occluded
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED
+ runTransition(KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+
+ // WHEN the keyguard is occluded and the lockscreen hosted dream stops
+ keyguardRepository.setIsActiveDreamLockscreenHosted(false)
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun dozingToLockscreen() =
testScope.runTest {
// GIVEN a prior transition has run to DOZING
@@ -533,6 +719,38 @@
}
@Test
+ fun goneToDreamingLockscreenHosted() =
+ testScope.runTest {
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to GONE
+ runTransition(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+
+ // WHEN the device begins to dream with the lockscreen hosted dream
+ keyguardRepository.setDreamingWithOverlay(true)
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
+ assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.GONE)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun alternateBouncerToPrimaryBouncer() =
testScope.runTest {
// GIVEN a prior transition has run to ALTERNATE_BOUNCER
@@ -726,6 +944,34 @@
}
@Test
+ fun primaryBouncerToDreamingLockscreenHosted() =
+ testScope.runTest {
+ // GIVEN device dreaming with the lockscreen hosted dream and not dozing
+ keyguardRepository.setIsActiveDreamLockscreenHosted(true)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+
+ // GIVEN a prior transition has run to PRIMARY_BOUNCER
+ bouncerRepository.setPrimaryShow(true)
+ runTransition(KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER)
+
+ // WHEN the primary bouncer stops showing and lockscreen hosted dream still active
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
+ assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun occludedToGone() =
testScope.runTest {
// GIVEN a device on lockscreen
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
index ca6a5b6..d825c2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -21,7 +21,6 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -91,7 +90,7 @@
@Test
fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -104,7 +103,7 @@
@Test
fun dismissLockScreen_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setUnlocked(true)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -117,7 +116,7 @@
@Test
fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setUnlocked(false)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -131,11 +130,11 @@
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen))
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
assertThat(isUnlocked).isFalse()
}
@@ -145,13 +144,13 @@
testScope.runTest {
val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
runCurrent()
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
assertThat(isUnlocked).isFalse()
}
@@ -159,10 +158,10 @@
@Test
fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings))
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index b019a21..6efec99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -38,7 +38,6 @@
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -68,6 +67,7 @@
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var burnInInteractor: BurnInInteractor
private lateinit var shadeRepository: FakeShadeRepository
+ private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -79,35 +79,32 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope()
configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- shadeRepository = FakeShadeRepository()
- fakeCommandQueue = FakeCommandQueue()
featureFlags =
FakeFeatureFlags().apply {
set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
set(Flags.FACE_AUTH_REFACTOR, false)
}
+ KeyguardInteractorFactory.create(featureFlags = featureFlags).let {
+ keyguardInteractor = it.keyguardInteractor
+ keyguardRepository = it.repository
+ }
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ shadeRepository = FakeShadeRepository()
+ fakeCommandQueue = FakeCommandQueue()
burnInInteractor =
BurnInInteractor(
context,
burnInHelper,
testScope.backgroundScope,
configRepository,
- FakeSystemClock(),
+ keyguardInteractor
)
underTest =
UdfpsKeyguardInteractor(
configRepository,
burnInInteractor,
- KeyguardInteractor(
- keyguardRepository,
- fakeCommandQueue,
- featureFlags,
- bouncerRepository,
- configRepository,
- ),
+ keyguardInteractor,
shadeRepository,
dialogManager,
)
@@ -215,7 +212,7 @@
private fun setAwake() {
keyguardRepository.setDozeAmount(0f)
- burnInInteractor.dozeTimeTick()
+ keyguardRepository.dozeTimeTick()
bouncerRepository.setAlternateVisible(false)
keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index ba8e0f2..63ee240 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -22,9 +22,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -51,20 +49,15 @@
private val underTest =
LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- interactorFactory =
- object : LockscreenSceneInteractor.Factory {
- override fun create(containerName: String): LockscreenSceneInteractor {
- return utils.lockScreenSceneInteractor(
+ interactor =
+ utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor =
+ utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- ),
- )
- }
- },
- containerName = CONTAINER_1
+ sceneInteractor = sceneInteractor,
+ ),
+ ),
)
@Test
@@ -116,7 +109,7 @@
@Test
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -129,7 +122,7 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -142,7 +135,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
@@ -155,7 +148,7 @@
@Test
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
index b985b3c..bd17de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt
@@ -20,21 +20,19 @@
import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -58,9 +56,9 @@
private lateinit var configRepository: FakeConfigurationRepository
private lateinit var bouncerRepository: KeyguardBouncerRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var featureFlags: FakeFeatureFlags
private lateinit var shadeRepository: FakeShadeRepository
+ private lateinit var keyguardInteractor: KeyguardInteractor
@Mock private lateinit var dialogManager: SystemUIDialogManager
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -70,17 +68,21 @@
MockitoAnnotations.initMocks(this)
overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
- fakeCommandQueue = FakeCommandQueue()
shadeRepository = FakeShadeRepository()
featureFlags =
FakeFeatureFlags().apply {
set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
set(Flags.FACE_AUTH_REFACTOR, false)
}
-
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
+ )
+ .also {
+ keyguardInteractor = it.keyguardInteractor
+ keyguardRepository = it.repository
+ configRepository = it.configurationRepository
+ bouncerRepository = it.bouncerRepository
+ }
val udfpsKeyguardInteractor =
UdfpsKeyguardInteractor(
configRepository,
@@ -89,15 +91,9 @@
burnInHelper,
testScope.backgroundScope,
configRepository,
- FakeSystemClock(),
+ keyguardInteractor,
),
- KeyguardInteractor(
- keyguardRepository,
- fakeCommandQueue,
- featureFlags,
- bouncerRepository,
- configRepository,
- ),
+ keyguardInteractor,
shadeRepository,
dialogManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 0fbcec2..80ab418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -30,12 +30,11 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-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.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -90,14 +89,10 @@
testScope.backgroundScope,
)
val keyguardInteractor =
- KeyguardInteractor(
- keyguardRepository,
- fakeCommandQueue,
- featureFlags,
- bouncerRepository,
- configRepository,
- )
-
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
+ )
+ .keyguardInteractor
underTest =
FingerprintViewModel(
context,
@@ -109,7 +104,7 @@
burnInHelper,
testScope.backgroundScope,
configRepository,
- FakeSystemClock(),
+ keyguardInteractor,
),
keyguardInteractor,
shadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 41ae931..0456824 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
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.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -39,7 +40,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.animation.Interpolators
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
@@ -72,6 +72,7 @@
private lateinit var transitionRepository: FakeKeyguardTransitionRepository
private lateinit var configRepository: FakeConfigurationRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var keyguardInteractor: KeyguardInteractor
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var shadeRepository: FakeShadeRepository
private lateinit var featureFlags: FakeFeatureFlags
@@ -81,23 +82,21 @@
MockitoAnnotations.initMocks(this)
testScope = TestScope()
transitionRepository = FakeKeyguardTransitionRepository()
- configRepository = FakeConfigurationRepository()
- keyguardRepository = FakeKeyguardRepository()
- bouncerRepository = FakeKeyguardBouncerRepository()
shadeRepository = FakeShadeRepository()
featureFlags =
FakeFeatureFlags().apply {
set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
set(Flags.FACE_AUTH_REFACTOR, false)
}
- val keyguardInteractor =
- KeyguardInteractor(
- keyguardRepository,
- commandQueue = mock(),
- featureFlags,
- bouncerRepository,
- configRepository,
+ KeyguardInteractorFactory.create(
+ featureFlags = featureFlags,
)
+ .also {
+ keyguardInteractor = it.keyguardInteractor
+ keyguardRepository = it.repository
+ configRepository = it.configurationRepository
+ bouncerRepository = it.bouncerRepository
+ }
underTest =
UdfpsLockscreenViewModel(
@@ -115,7 +114,7 @@
burnInHelperWrapper = mock(),
testScope.backgroundScope,
configRepository,
- FakeSystemClock(),
+ keyguardInteractor,
),
keyguardInteractor,
shadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 708bb55..1931580 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -98,8 +98,6 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaOutputControllerTest extends SysuiTestCase {
-
- private static final String TEST_PACKAGE_NAME = "com.test.package.name";
private static final String TEST_DEVICE_1_ID = "test_device_1_id";
private static final String TEST_DEVICE_2_ID = "test_device_2_id";
private static final String TEST_DEVICE_3_ID = "test_device_3_id";
@@ -167,6 +165,7 @@
final Notification mNotification = mock(Notification.class);
private Context mSpyContext;
+ private String mPackageName = null;
private MediaOutputController mMediaOutputController;
private LocalMediaManager mLocalMediaManager;
private List<MediaController> mMediaControllers = new ArrayList<>();
@@ -177,12 +176,14 @@
@Before
public void setUp() {
+ mPackageName = mContext.getPackageName();
+
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPackageManager);
mSpyContext = spy(mContext);
final UserHandle userHandle = mock(UserHandle.class);
when(mUserTracker.getUserHandle()).thenReturn(userHandle);
- when(mSessionMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(mSessionMediaController.getPackageName()).thenReturn(mPackageName);
when(mSessionMediaController.getPlaybackState()).thenReturn(mPlaybackState);
mMediaControllers.add(mSessionMediaController);
when(mMediaSessionManager.getActiveSessionsForUser(any(),
@@ -193,7 +194,7 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
- mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
+ mMediaOutputController = new MediaOutputController(mSpyContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
@@ -231,7 +232,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(mNotification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
mNotification.extras = bundle;
when(bundle.getParcelable(Notification.EXTRA_MEDIA_SESSION,
MediaSession.Token.class)).thenReturn(token);
@@ -339,8 +340,8 @@
public void tryToLaunchMediaApplication_intentNotNull_startActivity() {
when(mDialogLaunchAnimator.createActivityLaunchController(any(View.class))).thenReturn(
mController);
- Intent intent = new Intent(TEST_PACKAGE_NAME);
- doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(TEST_PACKAGE_NAME);
+ Intent intent = new Intent(mPackageName);
+ doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName);
mMediaOutputController.start(mCallback);
mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
@@ -355,7 +356,7 @@
mController);
mMediaOutputController.start(mCallback);
when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn(
- new ComponentName(TEST_PACKAGE_NAME, ""));
+ new ComponentName(mPackageName, ""));
mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView);
@@ -891,7 +892,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(notification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(null);
@@ -910,7 +911,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(notification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getLargeIcon()).thenReturn(icon);
@@ -929,7 +930,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(notification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
when(notification.isMediaNotification()).thenReturn(false);
when(notification.getLargeIcon()).thenReturn(icon);
@@ -947,7 +948,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(notification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(null);
@@ -966,7 +967,7 @@
when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
when(entry.getSbn()).thenReturn(sbn);
when(sbn.getNotification()).thenReturn(notification);
- when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(sbn.getPackageName()).thenReturn(mPackageName);
when(notification.isMediaNotification()).thenReturn(true);
when(notification.getSmallIcon()).thenReturn(icon);
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 ed7a59e..ee42a70 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
@@ -20,9 +20,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -48,26 +46,21 @@
private val underTest =
QuickSettingsSceneViewModel(
- lockscreenSceneInteractorFactory =
- object : LockscreenSceneInteractor.Factory {
- override fun create(containerName: String): LockscreenSceneInteractor {
- return utils.lockScreenSceneInteractor(
+ lockscreenSceneInteractor =
+ utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor =
+ utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- ),
- )
- }
- },
- containerName = CONTAINER_1
+ sceneInteractor = sceneInteractor,
+ ),
+ ),
)
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -80,7 +73,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 9ce378d..826a6cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -41,7 +41,7 @@
@Test
fun allSceneKeys() {
val underTest = utils.fakeSceneContainerRepository()
- assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
+ assertThat(underTest.allSceneKeys())
.isEqualTo(
listOf(
SceneKey.QuickSettings,
@@ -53,115 +53,58 @@
)
}
- @Test(expected = IllegalStateException::class)
- fun allSceneKeys_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.allSceneKeys("nonExistingContainer")
- }
-
@Test
fun currentScene() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test(expected = IllegalStateException::class)
- fun currentScene_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.currentScene("nonExistingContainer")
- }
-
- @Test(expected = IllegalStateException::class)
- fun setCurrentScene_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade))
- }
-
- @Test(expected = IllegalStateException::class)
fun setCurrentScene_noSuchSceneInContainer_throws() {
val underTest =
utils.fakeSceneContainerRepository(
- setOf(
- utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
- utils.fakeSceneContainerConfig(
- SceneTestUtils.CONTAINER_2,
- listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- ),
- )
+ utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
)
- underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
+ val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
+ underTest.setVisible(false)
assertThat(isVisible).isFalse()
- underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
+ underTest.setVisible(true)
assertThat(isVisible).isTrue()
}
- @Test(expected = IllegalStateException::class)
- fun isVisible_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.isVisible("nonExistingContainer")
- }
-
- @Test(expected = IllegalStateException::class)
- fun setVisible_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.setVisible("nonExistingContainer", false)
- }
-
@Test
- fun sceneTransitionProgress() = runTest {
+ fun transitionProgress() = runTest {
val underTest = utils.fakeSceneContainerRepository()
- val sceneTransitionProgress by
- collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
+ val sceneTransitionProgress by collectLastValue(underTest.transitionProgress)
assertThat(sceneTransitionProgress).isEqualTo(1f)
- underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
+ underTest.setSceneTransitionProgress(0.1f)
assertThat(sceneTransitionProgress).isEqualTo(0.1f)
- underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
+ underTest.setSceneTransitionProgress(0.9f)
assertThat(sceneTransitionProgress).isEqualTo(0.9f)
}
- @Test(expected = IllegalStateException::class)
- fun sceneTransitionProgress_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.sceneTransitionProgress("nonExistingContainer")
- }
-
@Test
fun setSceneTransition() = runTest {
- val underTest =
- utils.fakeSceneContainerRepository(
- setOf(
- utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
- utils.fakeSceneContainerConfig(
- SceneTestUtils.CONTAINER_2,
- listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- ),
- )
- )
- val sceneTransition by
- collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
+ val underTest = utils.fakeSceneContainerRepository()
+ val sceneTransition by collectLastValue(underTest.transitions)
assertThat(sceneTransition).isNull()
- underTest.setSceneTransition(
- SceneTestUtils.CONTAINER_2,
- SceneKey.Lockscreen,
- SceneKey.QuickSettings
- )
+ underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings)
assertThat(sceneTransition)
.isEqualTo(
SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
@@ -169,46 +112,20 @@
}
@Test(expected = IllegalStateException::class)
- fun setSceneTransition_noSuchContainer_throws() {
- val underTest = utils.fakeSceneContainerRepository()
- underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
- }
-
- @Test(expected = IllegalStateException::class)
fun setSceneTransition_noFromSceneInContainer_throws() {
val underTest =
utils.fakeSceneContainerRepository(
- setOf(
- utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
- utils.fakeSceneContainerConfig(
- SceneTestUtils.CONTAINER_2,
- listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- ),
- )
+ utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
)
- underTest.setSceneTransition(
- SceneTestUtils.CONTAINER_2,
- SceneKey.Shade,
- SceneKey.Lockscreen
- )
+ underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
}
@Test(expected = IllegalStateException::class)
fun setSceneTransition_noToSceneInContainer_throws() {
val underTest =
utils.fakeSceneContainerRepository(
- setOf(
- utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
- utils.fakeSceneContainerConfig(
- SceneTestUtils.CONTAINER_2,
- listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- ),
- )
+ utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
)
- underTest.setSceneTransition(
- SceneTestUtils.CONTAINER_2,
- SceneKey.Shade,
- SceneKey.Lockscreen
- )
+ underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
}
}
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 d2bbfa8..13a602d 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
@@ -41,48 +41,46 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
- .isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
}
@Test
fun currentScene() = runTest {
- val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(underTest.currentScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
}
@Test
fun sceneTransitionProgress() = runTest {
- val progress by
- collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
+ val progress by collectLastValue(underTest.transitionProgress)
assertThat(progress).isEqualTo(1f)
- underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
+ underTest.setSceneTransitionProgress(0.55f)
assertThat(progress).isEqualTo(0.55f)
}
@Test
fun isVisible() = runTest {
- val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
+ val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
+ underTest.setVisible(false)
assertThat(isVisible).isFalse()
- underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
+ underTest.setVisible(true)
assertThat(isVisible).isTrue()
}
@Test
fun sceneTransitions() = runTest {
- val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
+ val transitions by collectLastValue(underTest.transitions)
assertThat(transitions).isNull()
- val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
- underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+ val initialSceneKey = underTest.currentScene.value.key
+ underTest.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(transitions)
.isEqualTo(
SceneTransitionModel(
@@ -91,7 +89,7 @@
)
)
- underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+ underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings))
assertThat(transitions)
.isEqualTo(
SceneTransitionModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 6f6c5a5..b6bd31f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.model.SysUiState
import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.util.mockito.mock
@@ -47,7 +46,7 @@
@SmallTest
@RunWith(JUnit4::class)
-class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
+class SceneContainerStartableTest : SysuiTestCase() {
private val utils = SceneTestUtils(this)
private val testScope = utils.testScope
@@ -66,7 +65,7 @@
private val sysUiState: SysUiState = mock()
private val underTest =
- SystemUiDefaultSceneContainerStartable(
+ SceneContainerStartable(
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
authenticationInteractor = authenticationInteractor,
@@ -84,14 +83,8 @@
@Test
fun hydrateVisibility_featureEnabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
- val isVisible by
- collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
@@ -104,24 +97,15 @@
assertThat(isVisible).isFalse()
- sceneInteractor.setCurrentScene(
- SceneContainerNames.SYSTEM_UI_DEFAULT,
- SceneModel(SceneKey.Shade)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(isVisible).isTrue()
}
@Test
fun hydrateVisibility_featureDisabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
- val isVisible by
- collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+ val isVisible by collectLastValue(sceneInteractor.isVisible)
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = true,
@@ -133,28 +117,17 @@
underTest.start()
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(
- SceneContainerNames.SYSTEM_UI_DEFAULT,
- SceneModel(SceneKey.Gone)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
assertThat(isVisible).isTrue()
- sceneInteractor.setCurrentScene(
- SceneContainerNames.SYSTEM_UI_DEFAULT,
- SceneModel(SceneKey.Shade)
- )
+ sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(isVisible).isTrue()
}
@Test
fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
@@ -171,12 +144,7 @@
@Test
fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -193,12 +161,7 @@
@Test
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = false,
@@ -215,12 +178,7 @@
@Test
fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -237,12 +195,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isBypassEnabled = true,
@@ -259,12 +212,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isBypassEnabled = false,
@@ -281,12 +229,7 @@
@Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isBypassEnabled = true,
@@ -303,12 +246,7 @@
@Test
fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = true,
@@ -325,12 +263,7 @@
@Test
fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = true,
@@ -347,12 +280,7 @@
@Test
fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = true,
isDeviceUnlocked = false,
@@ -369,12 +297,7 @@
@Test
fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
testScope.runTest {
- val currentSceneKey by
- collectLastValue(
- sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
- it.key
- }
- )
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
prepareState(
isFeatureEnabled = false,
isDeviceUnlocked = false,
@@ -403,10 +326,7 @@
SceneKey.QuickSettings,
)
.forEachIndexed { index, sceneKey ->
- sceneInteractor.setCurrentScene(
- SceneContainerNames.SYSTEM_UI_DEFAULT,
- SceneModel(sceneKey),
- )
+ sceneInteractor.setCurrentScene(SceneModel(sceneKey))
runCurrent()
verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
@@ -422,9 +342,7 @@
featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
authenticationRepository.setUnlocked(isDeviceUnlocked)
keyguardRepository.setBypassEnabled(isBypassEnabled)
- initialSceneKey?.let {
- sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
- }
+ initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it)) }
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 63ea918c..0ab98ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -45,7 +45,6 @@
private val underTest =
SceneContainerViewModel(
interactor = interactor,
- containerName = SceneTestUtils.CONTAINER_1,
)
@Test
@@ -53,10 +52,10 @@
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
+ interactor.setVisible(false)
assertThat(isVisible).isFalse()
- interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
+ interactor.setVisible(true)
assertThat(isVisible).isTrue()
}
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 47ca49d0..9bcc8aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -99,6 +99,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.view.KeyguardRootView;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
@@ -297,7 +298,8 @@
@Mock protected LockscreenToOccludedTransitionViewModel
mLockscreenToOccludedTransitionViewModel;
@Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
-
+ @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel
+ mGoneToDreamingLockscreenHostedTransitionViewModel;
@Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -371,6 +373,7 @@
mKeyguardLogger,
mFeatureFlags,
mInteractionJankMonitor,
+ mKeyguardInteractor,
mDumpManager));
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
@@ -477,6 +480,20 @@
when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
.thenReturn(emptyFlow());
+ // Gone->Dreaming lockscreen hosted
+ when(mKeyguardTransitionInteractor.getGoneToDreamingLockscreenHostedTransition())
+ .thenReturn(emptyFlow());
+ when(mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha())
+ .thenReturn(emptyFlow());
+
+ // Dreaming lockscreen hosted->Lockscreen
+ when(mKeyguardTransitionInteractor.getDreamingLockscreenHostedToLockscreenTransition())
+ .thenReturn(emptyFlow());
+
+ // Lockscreen->Dreaming lockscreen hosted
+ when(mKeyguardTransitionInteractor.getLockscreenToDreamingLockscreenHostedTransition())
+ .thenReturn(emptyFlow());
+
// Lockscreen->Occluded
when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition())
.thenReturn(emptyFlow());
@@ -612,6 +629,7 @@
mOccludedToLockscreenTransitionViewModel,
mLockscreenToDreamingTransitionViewModel,
mGoneToDreamingTransitionViewModel,
+ mGoneToDreamingLockscreenHostedTransitionViewModel,
mLockscreenToOccludedTransitionViewModel,
mMainDispatcher,
mKeyguardTransitionInteractor,
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 6e9fba6..8739b28 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
@@ -20,7 +20,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -48,20 +47,15 @@
private val underTest =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- lockscreenSceneInteractorFactory =
- object : LockscreenSceneInteractor.Factory {
- override fun create(containerName: String): LockscreenSceneInteractor {
- return utils.lockScreenSceneInteractor(
+ lockscreenSceneInteractor =
+ utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractor =
+ utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
- bouncerInteractor =
- utils.bouncerInteractor(
- authenticationInteractor = authenticationInteractor,
- sceneInteractor = sceneInteractor,
- ),
- )
- }
- },
- containerName = SceneTestUtils.CONTAINER_1
+ sceneInteractor = sceneInteractor,
+ ),
+ ),
)
@Test
@@ -87,8 +81,7 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(true)
runCurrent()
@@ -101,8 +94,7 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by
- collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
utils.authenticationRepository.setUnlocked(false)
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index 0b2da8b..0cfca61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -24,6 +24,7 @@
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
@@ -38,6 +39,7 @@
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -50,6 +52,7 @@
class SystemEventChipAnimationControllerTest : SysuiTestCase() {
private lateinit var controller: SystemEventChipAnimationController
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Mock private lateinit var sbWindowController: StatusBarWindowController
@Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 1dc8453..ac8b42a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -789,6 +789,50 @@
assertThat(row.isExpanded()).isTrue();
}
+ @Test
+ public void onDisappearAnimationFinished_shouldSetFalse_headsUpAnimatingAway()
+ throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ // Initial state: suppose heads up animation in progress
+ row.setHeadsUpAnimatingAway(true);
+ assertThat(row.isHeadsUpAnimatingAway()).isTrue();
+
+ // on disappear animation ends
+ row.onAppearAnimationFinished(/* wasAppearing = */ false);
+ assertThat(row.isHeadsUpAnimatingAway()).isFalse();
+ }
+
+ @Test
+ public void onHUNAppear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ true);
+
+ waitForIdleSync();
+ assertThat(row.isDrawingAppearAnimation()).isTrue();
+
+ row.cancelAppearDrawing();
+
+ waitForIdleSync();
+ assertThat(row.isDrawingAppearAnimation()).isFalse();
+ }
+
+ @Test
+ public void onHUNDisappear_cancelAppearDrawing_shouldResetAnimationState() throws Exception {
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+
+ row.performAddAnimation(/* delay */ 0, /* duration */ 1000, /* isHeadsUpAppear */ false);
+
+ waitForIdleSync();
+ assertThat(row.isDrawingAppearAnimation()).isTrue();
+
+ row.cancelAppearDrawing();
+
+ waitForIdleSync();
+ assertThat(row.isDrawingAppearAnimation()).isFalse();
+ }
+
private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
Drawable rightIconDrawable) {
ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 91aa138..481f7f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -199,7 +199,7 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mKeyguardViewMediator).onWakeAndUnlocking();
+ verify(mKeyguardViewMediator).onWakeAndUnlocking(false);
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
}
@@ -217,7 +217,7 @@
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mKeyguardViewMediator).onWakeAndUnlocking();
+ verify(mKeyguardViewMediator).onWakeAndUnlocking(false);
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(MODE_WAKE_AND_UNLOCK);
}
@@ -671,8 +671,9 @@
when(mWakefulnessLifecycle.getLastWakeReason())
.thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
givenDreamingLocked();
+ when(mPowerManager.isInteractive()).thenReturn(true);
mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
- verify(mKeyguardViewMediator).onWakeAndUnlocking();
+ verify(mKeyguardViewMediator).onWakeAndUnlocking(true);
// Ensure that the power hasn't been told to wake up yet.
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 57037e0..ff6f40d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -43,7 +43,6 @@
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.ShadeViewController;
@@ -96,7 +95,6 @@
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
- @Mock private BurnInInteractor mBurnInInteractor;
@Mock private DozeInteractor mDozeInteractor;
@Before
@@ -108,8 +106,7 @@
() -> mAssistManager, mDozeScrimController,
mKeyguardUpdateMonitor, mPulseExpansionHandler,
mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mAuthController, mNotificationIconAreaController, mDozeInteractor,
- mBurnInInteractor);
+ mAuthController, mNotificationIconAreaController, mDozeInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
@@ -234,11 +231,11 @@
verifyZeroInteractions(mDozeInteractor);
}
@Test
- public void dozeTimeTickSentTBurnInInteractor() {
+ public void dozeTimeTickSentToDozeInteractor() {
// WHEN dozeTimeTick
mDozeServiceHost.dozeTimeTick();
- // THEN burnInInteractor's dozeTimeTick is updated
- verify(mBurnInInteractor).dozeTimeTick();
+ // THEN dozeInteractor's dozeTimeTick is updated
+ verify(mDozeInteractor).dozeTimeTick();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index c06dbdc..7f3d4b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -14,6 +14,7 @@
package com.android.systemui.statusbar.policy;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -24,10 +25,10 @@
import android.app.NotificationManager;
import android.os.Handler;
-import android.os.Looper;
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
@@ -45,6 +46,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -61,9 +65,10 @@
DumpManager mDumpManager;
@Mock
UserTracker mUserTracker;
-
private ZenModeControllerImpl mController;
+ private final FakeSettings mGlobalSettings = new FakeSettings();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -72,10 +77,10 @@
mController = new ZenModeControllerImpl(
mContext,
- Handler.createAsync(Looper.myLooper()),
+ Handler.createAsync(TestableLooper.get(this).getLooper()),
mBroadcastDispatcher,
mDumpManager,
- new FakeSettings(),
+ mGlobalSettings,
mUserTracker);
}
@@ -131,4 +136,48 @@
mController.addCallback(null);
mController.fireConfigChanged(null);
}
+
+ @Test
+ public void testModeChange() {
+ List<Integer> states = List.of(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+ Settings.Global.ZEN_MODE_ALARMS,
+ Settings.Global.ZEN_MODE_ALARMS
+ );
+
+ for (Integer state : states) {
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(state.intValue(), mController.getZen());
+ }
+ }
+
+ @Test
+ public void testModeChange_callbackNotified() {
+ final AtomicInteger currentState = new AtomicInteger(-1);
+
+ ZenModeController.Callback callback = new Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ currentState.set(zen);
+ }
+ };
+
+ mController.addCallback(callback);
+
+ List<Integer> states = List.of(
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+ Settings.Global.ZEN_MODE_ALARMS,
+ Settings.Global.ZEN_MODE_ALARMS
+ );
+
+ for (Integer state : states) {
+ mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(state.intValue(), currentState.get());
+ }
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
new file mode 100644
index 0000000..f1fadf6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SolidColorShaderTest.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SolidColorShaderTest : SysuiTestCase() {
+
+ @Test
+ fun compilesShader() {
+ SolidColorShader(Color.RED)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
new file mode 100644
index 0000000..64ea6a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/shaders/SparkleShaderTest.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.surfaceeffects.shaders
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SparkleShaderTest : SysuiTestCase() {
+
+ private lateinit var sparkleShader: SparkleShader
+
+ @Test
+ fun compilesSparkleShader() {
+ sparkleShader =
+ SparkleShader().apply {
+ setPixelateAmount(
+ context.resources.displayMetrics.density *
+ SparkleShader.DEFAULT_SPARKLE_PIXELATE_AMOUNT
+ )
+ setColor(Color.RED)
+ setTime(0.01f)
+ setLumaMatteColor(Color.WHITE)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 09ac0e3..c454b45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -959,8 +959,8 @@
// All dynamic colors were added twice: light and dark them
// All fixed colors were added once
verify(dynamic, times(
- DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2
- + DynamicColors.FIXED_COLORS_MAPPED.size())
+ DynamicColors.allDynamicColorsMapped(false).size() * 2
+ + DynamicColors.getFixedColorsMapped(false).size())
).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 0663004..462fd0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -40,7 +40,6 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.CaptioningManager;
import androidx.test.filters.SmallTest;
@@ -64,6 +63,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.Executor;
+
@RunWith(AndroidTestingRunner.class)
@SmallTest
@TestableLooper.RunWithLooper
@@ -96,8 +97,6 @@
@Mock
private WakefulnessLifecycle mWakefullnessLifcycle;
@Mock
- private CaptioningManager mCaptioningManager;
- @Mock
private KeyguardManager mKeyguardManager;
@Mock
private ActivityManager mActivityManager;
@@ -105,6 +104,8 @@
private UserTracker mUserTracker;
@Mock
private DumpManager mDumpManager;
+ @Mock
+ private Handler mHandler;
@Before
@@ -117,6 +118,7 @@
when(mRingerModeLiveData.getValue()).thenReturn(-1);
when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
+ when(mUserTracker.getUserContext()).thenReturn(mContext);
// Enable group volume adjustments
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions,
@@ -127,8 +129,8 @@
mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
- mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
- mActivityManager, mUserTracker, mDumpManager, mCallback);
+ mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
+ mActivityManager, mUserTracker, mDumpManager, mHandler, mCallback);
mVolumeController.setEnableDialogs(true, true);
}
@@ -219,6 +221,11 @@
verify(mRingerModeInternalLiveData).observeForever(any());
}
+ @Test
+ public void testAddCallbackWithUserTracker() {
+ verify(mUserTracker).addCallback(any(UserTracker.Callback.class), any(Executor.class));
+ }
+
static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
private final WakefulnessLifecycle.Observer mWakefullessLifecycleObserver;
@@ -234,16 +241,16 @@
AccessibilityManager accessibilityManager,
PackageManager packageManager,
WakefulnessLifecycle wakefulnessLifecycle,
- CaptioningManager captioningManager,
KeyguardManager keyguardManager,
ActivityManager activityManager,
UserTracker userTracker,
DumpManager dumpManager,
+ Handler mainHandler,
C callback) {
super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
notificationManager, optionalVibrator, iAudioService, accessibilityManager,
- packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
- activityManager, userTracker, dumpManager);
+ packageManager, wakefulnessLifecycle, keyguardManager,
+ activityManager, userTracker, dumpManager, mainHandler);
mCallbacks = callback;
ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
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 2f228a8..e10a80c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -158,7 +158,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -174,7 +173,6 @@
import java.util.List;
import java.util.Optional;
-@Ignore("b/292153259")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -280,8 +278,6 @@
@Mock
private TaskStackListenerImpl mTaskStackListener;
@Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private ScreenOffAnimationController mScreenOffAnimationController;
@@ -298,6 +294,7 @@
@Mock
private Icon mAppBubbleIcon;
+ private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
private TestableBubblePositioner mPositioner;
@@ -379,7 +376,13 @@
mock(UiEventLogger.class),
mock(UserTracker.class)
);
- when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
+
+ mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class),
+ mock(ShellCommandHandler.class),
+ null,
+ Optional.empty(),
+ Optional.empty(),
+ syncExecutor);
mBubbleProperties = new FakeBubbleProperties();
mBubbleController = new TestableBubbleController(
mContext,
diff --git a/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.kt
new file mode 100644
index 0000000..026372f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.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 androidx.core.animation
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class AndroidXAnimatorIsolationRule : TestRule {
+
+ private class TestAnimationHandler : AnimationHandler(null) {
+ override fun addAnimationFrameCallback(callback: AnimationFrameCallback?) = doFail()
+ override fun removeCallback(callback: AnimationFrameCallback?) = doFail()
+ override fun onAnimationFrame(frameTime: Long) = doFail()
+ override fun setFrameDelay(frameDelay: Long) = doFail()
+ override fun getFrameDelay(): Long = doFail()
+ }
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return object : Statement() {
+ @Throws(Throwable::class)
+ override fun evaluate() {
+ AnimationHandler.setTestHandler(testHandler)
+ try {
+ base.evaluate()
+ } finally {
+ AnimationHandler.setTestHandler(null)
+ }
+ }
+ }
+ }
+
+ companion object {
+ private val testHandler = TestAnimationHandler()
+ private fun doFail(): Nothing =
+ error(
+ "Test's animations are not isolated! " +
+ "Did you forget to add an AnimatorTestRule to your test class?"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index de177168..28b7d41 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -33,6 +33,7 @@
import android.testing.TestableLooper;
import android.util.Log;
+import androidx.core.animation.AndroidXAnimatorIsolationRule;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
@@ -52,6 +53,7 @@
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.mockito.Mockito;
@@ -69,6 +71,12 @@
private static final String TAG = "SysuiTestCase";
private Handler mHandler;
+
+ // set the lowest order so it's the outermost rule
+ @ClassRule(order = Integer.MIN_VALUE)
+ public static AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
+ new AndroidXAnimatorIsolationRule();
+
@Rule
public SysuiTestableContext mContext = new SysuiTestableContext(
InstrumentationRegistry.getContext(), getLeakCheck());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index f4c2db1..1e1dc4f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -61,6 +61,19 @@
_wasDisabled = true
}
+ private val faceAuthPaused = MutableStateFlow(false)
+ override fun pauseFaceAuth() {
+ faceAuthPaused.value = true
+ }
+
+ override fun resumeFaceAuth() {
+ faceAuthPaused.value = false
+ }
+
+ fun isFaceAuthPaused(): Boolean {
+ return faceAuthPaused.value
+ }
+
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
_runningAuthRequest.value = uiEvent to fallbackToDetection
_isAuthRunning.value = true
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index e6894d7..15ce055 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -30,7 +30,6 @@
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -60,7 +59,7 @@
private val _isDozing = MutableStateFlow(false)
override val isDozing: StateFlow<Boolean> = _isDozing
- private val _dozeTimeTick = MutableSharedFlow<Unit>()
+ private val _dozeTimeTick = MutableStateFlow<Long>(0L)
override val dozeTimeTick = _dozeTimeTick
private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
@@ -174,7 +173,11 @@
}
override fun dozeTimeTick() {
- _dozeTimeTick.tryEmit(Unit)
+ _dozeTimeTick.value = _dozeTimeTick.value + 1
+ }
+
+ fun dozeTimeTick(millis: Long) {
+ _dozeTimeTick.value = millis
}
override fun setLastDozeTapToWakePosition(position: Point) {
@@ -237,6 +240,10 @@
_isBypassEnabled = isEnabled
}
+ fun setScreenModel(screenModel: ScreenModel) {
+ _screenModel.value = screenModel
+ }
+
override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.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 f39982f..26a75d0 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
@@ -41,7 +41,6 @@
import com.android.systemui.scene.shared.model.RemoteUserInput
import com.android.systemui.scene.shared.model.RemoteUserInputAction
import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneContainerNames
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.UserRepository
@@ -96,13 +95,9 @@
private val context = test.context
fun fakeSceneContainerRepository(
- containerConfigurations: Set<SceneContainerConfig> =
- setOf(
- fakeSceneContainerConfig(CONTAINER_1),
- fakeSceneContainerConfig(CONTAINER_2),
- )
+ containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
): SceneContainerRepository {
- return SceneContainerRepository(containerConfigurations.associateBy { it.name })
+ return SceneContainerRepository(containerConfig)
}
fun fakeSceneKeys(): List<SceneKey> {
@@ -116,11 +111,9 @@
}
fun fakeSceneContainerConfig(
- name: String,
sceneKeys: List<SceneKey> = fakeSceneKeys(),
): SceneContainerConfig {
return SceneContainerConfig(
- name = name,
sceneKeys = sceneKeys,
initialSceneKey = SceneKey.Lockscreen,
)
@@ -174,7 +167,6 @@
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
featureFlags = featureFlags,
- containerName = CONTAINER_1,
)
}
@@ -184,14 +176,8 @@
return BouncerViewModel(
applicationContext = context,
applicationScope = applicationScope(),
- interactorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return bouncerInteractor
- }
- },
+ interactor = bouncerInteractor,
featureFlags = featureFlags,
- containerName = CONTAINER_1,
)
}
@@ -202,13 +188,7 @@
return LockscreenSceneInteractor(
applicationScope = applicationScope(),
authenticationInteractor = authenticationInteractor,
- bouncerInteractorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return bouncerInteractor
- }
- },
- containerName = CONTAINER_1,
+ bouncerInteractor = bouncerInteractor,
)
}
@@ -217,9 +197,6 @@
}
companion object {
- const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
- const val CONTAINER_2 = "container2"
-
val REMOTE_INPUT_DOWN_GESTURE =
listOf(
RemoteUserInput(10f, 10f, RemoteUserInputAction.DOWN),
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 777c7c8..9b9593b 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1344,6 +1344,8 @@
Slog.e(LOG_TAG, "Unable to find a valid pointer for touch exploration.");
return;
}
+ // Send hover exit if we haven't closed a previous touch exploration event stream.
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(pointerId);
final int pointerIdBits = (1 << pointerId);
final int policyFlags = mState.getLastReceivedPolicyFlags();
mSendHoverEnterAndMoveDelayed.setPointerIdBits(pointerIdBits);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index d33d224..805f6e3 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -110,6 +110,7 @@
private boolean mAlwaysOnMagnificationEnabled = false;
private final DisplayManagerInternal mDisplayManagerInternal;
+ private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
@NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
/**
@@ -689,6 +690,13 @@
}
}
+ void onThumbnailFeatureFlagChanged() {
+ synchronized (mLock) {
+ destroyThumbnail();
+ createThumbnailIfSupported();
+ }
+ }
+
/**
* Updates the current magnification spec.
*
@@ -849,19 +857,43 @@
addInfoChangedCallback(magnificationInfoChangedCallback);
mScaleProvider = scaleProvider;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+ mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
+ mMagnificationThumbnailFeatureFlag.addOnChangedListener(
+ backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged);
if (thumbnailSupplier != null) {
mThumbnailSupplier = thumbnailSupplier;
} else {
mThumbnailSupplier = () -> {
- return new MagnificationThumbnail(
- ctx.getContext(),
- ctx.getContext().getSystemService(WindowManager.class),
- new Handler(ctx.getContext().getMainLooper())
- );
+ if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
+ return new MagnificationThumbnail(
+ ctx.getContext(),
+ ctx.getContext().getSystemService(WindowManager.class),
+ new Handler(ctx.getContext().getMainLooper())
+ );
+ }
+ return null;
};
}
}
+ private void onMagnificationThumbnailFeatureFlagChanged() {
+ synchronized (mLock) {
+ for (int i = 0; i < mDisplays.size(); i++) {
+ onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
+ }
+ }
+ }
+
+ private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
+ synchronized (mLock) {
+ final DisplayMagnification display = mDisplays.get(displayId);
+ if (display == null) {
+ return;
+ }
+ display.onThumbnailFeatureFlagChanged();
+ }
+ }
+
/**
* Start tracking the magnification region for services that control magnification and the
* magnification gesture handler.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
new file mode 100644
index 0000000..519f31b
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
@@ -0,0 +1,46 @@
+/*
+ * 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.accessibility.magnification;
+
+import android.provider.DeviceConfig;
+
+/**
+ * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
+ *
+ * @hide
+ */
+public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
+
+ private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
+ private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
+ "enable_magnifier_thumbnail";
+
+ @Override
+ String getNamespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ String getFeatureName() {
+ return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
+ }
+
+ @Override
+ boolean getDefaultValue() {
+ return false;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index be52b1c..82d4d60 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -21,6 +21,7 @@
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
+import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
@@ -640,29 +641,44 @@
}
@Override
+ @EnforcePermission(USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+ addOnTransportsChangedListener_enforcePermission();
+
mTransportManager.addListener(listener);
}
@Override
+ @EnforcePermission(USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+ removeOnTransportsChangedListener_enforcePermission();
+
mTransportManager.removeListener(listener);
}
@Override
+ @EnforcePermission(USE_COMPANION_TRANSPORTS)
public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+ sendMessage_enforcePermission();
+
mTransportManager.sendMessage(messageType, data, associationIds);
}
@Override
+ @EnforcePermission(USE_COMPANION_TRANSPORTS)
public void addOnMessageReceivedListener(int messageType,
IOnMessageReceivedListener listener) {
+ addOnMessageReceivedListener_enforcePermission();
+
mTransportManager.addListener(messageType, listener);
}
@Override
+ @EnforcePermission(USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
IOnMessageReceivedListener listener) {
+ removeOnMessageReceivedListener_enforcePermission();
+
mTransportManager.removeListener(messageType, listener);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index c79466f..94cede8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,6 +16,8 @@
package com.android.server.companion;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Telecom;
@@ -30,7 +32,6 @@
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.companion.transport.Transport;
import java.io.PrintWriter;
import java.util.List;
@@ -135,7 +136,7 @@
case "send-context-sync-empty-message": {
associationId = getNextIntArgRequired();
mTransportManager.createEmulatedTransport(associationId)
- .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+ .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
/* sequence= */ 0,
CrossDeviceSyncController.createEmptyMessage());
break;
@@ -147,7 +148,7 @@
String address = getNextArgRequired();
String facilitator = getNextArgRequired();
mTransportManager.createEmulatedTransport(associationId)
- .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+ .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
/* sequence= */ 0,
CrossDeviceSyncController.createCallCreateMessage(callId,
address, facilitator));
@@ -159,7 +160,7 @@
String callId = getNextArgRequired();
int control = getNextIntArgRequired();
mTransportManager.createEmulatedTransport(associationId)
- .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+ .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
/* sequence= */ 0,
CrossDeviceSyncController.createCallControlMessage(callId,
control));
@@ -184,7 +185,7 @@
}
pos.end(telecomToken);
mTransportManager.createEmulatedTransport(associationId)
- .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+ .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
/* sequence= */ 0, pos.getBytes());
break;
}
@@ -246,7 +247,7 @@
pos.end(callsToken);
pos.end(telecomToken);
mTransportManager.createEmulatedTransport(associationId)
- .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+ .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
/* sequence= */ 0, pos.getBytes());
break;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 7af4957..13f41ed 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -19,10 +19,10 @@
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index 9bd5af9..ad1eff8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -16,7 +16,7 @@
package com.android.server.companion.datatransfer.contextsync;
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 41867f9..f648f09 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,7 +16,7 @@
package com.android.server.companion.transport;
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 5af3b98..32d4061 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -16,6 +16,11 @@
package com.android.server.companion.transport;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_REMOTE_AUTHENTICATION;
+
import android.annotation.NonNull;
import android.companion.IOnMessageReceivedListener;
import android.content.Context;
@@ -45,10 +50,6 @@
protected static final String TAG = "CDM_CompanionTransport";
protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
- static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
- public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
- public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
-
static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
@@ -181,7 +182,8 @@
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
break;
}
- case MESSAGE_REQUEST_CONTEXT_SYNC: {
+ case MESSAGE_REQUEST_CONTEXT_SYNC:
+ case MESSAGE_REQUEST_REMOTE_AUTHENTICATION: {
callback(message, data);
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
break;
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index f19f7f2..1741593 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -19,12 +19,10 @@
import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.Sensor;
@@ -40,8 +38,6 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
import android.provider.Settings;
import android.util.MutableBoolean;
import android.util.Slog;
@@ -113,19 +109,6 @@
*/
private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
- /** Action for starting emergency alerts on Wear OS. */
- private static final String WEAR_LAUNCH_EMERGENCY_ACTION =
- "com.android.systemui.action.LAUNCH_EMERGENCY";
-
- /** Action for starting emergency alerts in retail mode on Wear OS. */
- private static final String WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION =
- "com.android.systemui.action.LAUNCH_EMERGENCY_RETAIL";
-
- /**
- * Boolean extra for distinguishing intents coming from power button gesture.
- */
- private static final String EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE = "launch_emergency_via_gesture";
-
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
@@ -198,7 +181,6 @@
private final UiEventLogger mUiEventLogger;
private boolean mHasFeatureWatch;
- private long mVibrateMilliSecondsForPanicGesture;
@VisibleForTesting
public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@@ -268,13 +250,6 @@
mHasFeatureWatch =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- mVibrateMilliSecondsForPanicGesture =
- resources.getInteger(
- com.android
- .internal
- .R
- .integer
- .config_mashPressVibrateTimeOnPowerButton);
}
}
@@ -714,11 +689,6 @@
userSetupComplete));
}
- if (mHasFeatureWatch) {
- onEmergencyGestureDetectedOnWatch();
- return true;
- }
-
StatusBarManagerInternal service = LocalServices.getService(
StatusBarManagerInternal.class);
service.onEmergencyActionLaunchGestureDetected();
@@ -728,37 +698,6 @@
}
}
- private void onEmergencyGestureDetectedOnWatch() {
- Intent emergencyIntent =
- new Intent(
- isInRetailMode()
- ? WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION
- : WEAR_LAUNCH_EMERGENCY_ACTION);
- PackageManager pm = mContext.getPackageManager();
- ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
- if (resolveInfo == null) {
- Slog.w(TAG, "Couldn't find an app to process the emergency intent "
- + emergencyIntent.getAction());
- return;
- }
-
- Vibrator vibrator = mContext.getSystemService(Vibrator.class);
- vibrator.vibrate(VibrationEffect.createOneShot(mVibrateMilliSecondsForPanicGesture,
- VibrationEffect.DEFAULT_AMPLITUDE));
-
- emergencyIntent.setComponent(
- new ComponentName(
- resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
- emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- emergencyIntent.putExtra(EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE, true);
- mContext.startActivityAsUser(emergencyIntent, new UserHandle(mUserId));
- }
-
- private boolean isInRetailMode() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.DEVICE_DEMO_MODE, 0) == 1;
- }
-
private boolean isUserSetupComplete() {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dc83125..383bb25 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6434,6 +6434,7 @@
}
updateServiceConnectionActivitiesLocked(psr);
psr.removeAllConnections();
+ psr.removeAllSdkSandboxConnections();
psr.mAllowlistManager = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index bef53c7..faf1900a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -22,6 +22,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean;
import android.annotation.NonNull;
import android.app.ActivityThread;
@@ -153,6 +154,11 @@
static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
static final String KEY_USE_MODERN_TRIM = "use_modern_trim";
+ /**
+ * Whether or not to enable the new oom adjuster implementation.
+ */
+ static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj";
+
private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -216,6 +222,11 @@
private static final boolean DEFAULT_USE_MODERN_TRIM = true;
/**
+ * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
+ */
+ private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+
+ /**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
*/
private static final int
@@ -1051,6 +1062,9 @@
/** @see #KEY_USE_MODERN_TRIM */
public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM;
+ /** @see #KEY_ENABLE_NEW_OOMADJ */
+ public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ;
+
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -1308,6 +1322,7 @@
CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
- rawMaxEmptyProcesses) / 3;
+ loadNativeBootDeviceConfigConstants();
}
public void start(ContentResolver resolver) {
@@ -1347,6 +1362,11 @@
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS));
}
+ private void loadNativeBootDeviceConfigConstants() {
+ ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ,
+ DEFAULT_ENABLE_NEW_OOM_ADJ);
+ }
+
public void setOverrideMaxCachedProcesses(int value) {
mOverrideMaxCachedProcesses = value;
updateMaxCachedProcesses();
@@ -1997,6 +2017,13 @@
DEFAULT_USE_MODERN_TRIM);
}
+ private void updateEnableNewOomAdj() {
+ ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+ KEY_ENABLE_NEW_OOMADJ,
+ DEFAULT_ENABLE_NEW_OOM_ADJ);
+ }
+
private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) {
ForegroundServiceTypePolicy.getDefaultPolicy()
.updatePermissionEnforcementFlagIfNecessary(name);
@@ -2187,6 +2214,9 @@
pw.print(" "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
+ pw.print(" "); pw.print(KEY_ENABLE_NEW_OOMADJ);
+ pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
+
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a0fae26..c1f2f67 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2029,6 +2029,7 @@
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
+ mOomAdjuster.onProcessBeginLocked(app);
updateLruProcessLocked(app, false, null);
updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
}
@@ -2422,7 +2423,9 @@
mProcessList.init(this, activeUids, mPlatformCompat);
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread);
+ mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+ ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
+ : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
@@ -2483,7 +2486,9 @@
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
new LowMemDetector(this));
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
+ mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+ ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
+ : new OomAdjuster(this, mProcessList, activeUids);
// Broadcast policy parameters
final BroadcastConstants foreConstants = new BroadcastConstants(
@@ -4595,6 +4600,7 @@
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
synchronized (mProcLock) {
+ mOomAdjuster.onProcessBeginLocked(app);
mOomAdjuster.setAttachingProcessStatesLSP(app);
clearProcessForegroundLocked(app);
app.setDebugging(false);
@@ -6980,6 +6986,7 @@
sdkSandboxClientAppPackage,
new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
customProcess != null ? customProcess : info.processName));
+ mOomAdjuster.onProcessBeginLocked(app);
updateLruProcessLocked(app, false, null);
updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 8c1fd51..2fff79b 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -373,7 +373,7 @@
* Return the {@link SystemProperty} name for the given key in our
* {@link DeviceConfig} namespace.
*/
- private @NonNull String propertyFor(@NonNull String key) {
+ private static @NonNull String propertyFor(@NonNull String key) {
return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
}
@@ -382,11 +382,11 @@
* {@link DeviceConfig} namespace, but with a different prefix that can be
* used to locally override the {@link DeviceConfig} value.
*/
- private @NonNull String propertyOverrideFor(@NonNull String key) {
+ private static @NonNull String propertyOverrideFor(@NonNull String key) {
return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
}
- private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
+ static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
return SystemProperties.getBoolean(propertyOverrideFor(key),
SystemProperties.getBoolean(propertyFor(key), def));
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 459c6ff..1f9e89e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,7 @@
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
@@ -124,6 +125,7 @@
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
@@ -369,20 +371,21 @@
*/
private final Handler mProcessGroupHandler;
- private final int[] mTmpSchedGroup = new int[1];
+ protected final int[] mTmpSchedGroup = new int[1];
- private final ActivityManagerService mService;
- private final ProcessList mProcessList;
- private final ActivityManagerGlobalLock mProcLock;
+ final ActivityManagerService mService;
+ final ProcessList mProcessList;
+ final ActivityManagerGlobalLock mProcLock;
private final int mNumSlots;
- private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
- private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
- private final ActiveUids mTmpUidRecords;
- private final ArrayDeque<ProcessRecord> mTmpQueue;
- private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
- private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
- private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
+ protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
+ protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>();
+ protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
+ protected final ActiveUids mTmpUidRecords;
+ protected final ArrayDeque<ProcessRecord> mTmpQueue;
+ protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
+ protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
+ protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
/**
* Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate
@@ -412,7 +415,7 @@
this(service, processList, activeUids, createAdjusterThread());
}
- private static ServiceThread createAdjusterThread() {
+ static ServiceThread createAdjusterThread() {
// The process group is usually critical to the response time of foreground app, so the
// setter should apply it as soon as possible.
final ServiceThread adjusterThread =
@@ -532,7 +535,7 @@
mPendingProcessSet.remove(app);
mProcessesInCycle.clear();
- computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true);
+ computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true);
if (!mProcessesInCycle.isEmpty()) {
// We can't use the score here if there is a cycle, abort.
for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) {
@@ -550,7 +553,7 @@
&& (uidRec.getSetProcState() != uidRec.getCurProcState()
|| uidRec.getSetCapability() != uidRec.getCurCapability()
|| uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
- ActiveUids uids = mTmpUidRecords;
+ final ActiveUids uids = mTmpUidRecords;
uids.clear();
uids.put(uidRec.getUid(), uidRec);
updateUidsLSP(uids, SystemClock.elapsedRealtime());
@@ -633,19 +636,20 @@
}
@GuardedBy({"mService", "mProcLock"})
- private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
mAdjSeq++;
- // Firstly, try to see if the importance of itself gets changed
final ProcessStateRecord state = app.mState;
final boolean wasCached = state.isCached();
final int oldAdj = state.getCurRawAdj();
final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
? oldAdj : UNKNOWN_ADJ;
+
+ // Firstly, try to see if the importance of itself gets changed
final boolean wasBackground = ActivityManager.isProcStateBackground(
state.getSetProcState());
final int oldCap = state.getSetCapability();
@@ -693,8 +697,6 @@
mPendingProcessSet.clear();
if (!containsCycle) {
- // Reset the flag
- state.setReachable(false);
// Remove this app from the return list because we've done the computation on it.
processes.remove(app);
}
@@ -718,8 +720,13 @@
return true;
}
+ /**
+ * Collect the reachable processes from the given {@code apps}, the result will be
+ * returned in the given {@code processes}, which will include the processes from
+ * the given {@code apps}.
+ */
@GuardedBy("mService")
- private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+ protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
ArrayList<ProcessRecord> processes, ActiveUids uids) {
final ArrayDeque<ProcessRecord> queue = mTmpQueue;
queue.clear();
@@ -824,11 +831,15 @@
if (size > 0) {
// Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it.
for (int l = 0, r = size - 1; l < r; l++, r--) {
- ProcessRecord t = processes.get(l);
- processes.set(l, processes.get(r));
+ final ProcessRecord t = processes.get(l);
+ final ProcessRecord u = processes.get(r);
+ t.mState.setReachable(false);
+ u.mState.setReachable(false);
+ processes.set(l, u);
processes.set(r, t);
}
}
+
return containsCycle;
}
@@ -928,24 +939,18 @@
* Update OomAdj for all processes within the given list (could be partial), or the whole LRU
* list if the given list is null; when it's partial update, each process's client proc won't
* get evaluated recursively here.
+ *
+ * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller
+ * must have called {@link collectReachableProcessesLocked} on it.
*/
@GuardedBy({"mService", "mProcLock"})
- private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
- if (startProfiling) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
- }
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
final boolean fullUpdate = processes == null;
+ final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+ ? mProcessList.getLruProcessesLOSP() : processes;
ActiveUids activeUids = uids;
- ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP()
- : processes;
- final int numProc = activeProcesses.size();
-
if (activeUids == null) {
final int numUids = mActiveUids.size();
activeUids = mTmpUidRecords;
@@ -956,14 +961,14 @@
}
}
- // Reset state in all uid records.
- for (int i = activeUids.size() - 1; i >= 0; i--) {
- final UidRecord uidRec = activeUids.valueAt(i);
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
+ if (startProfiling) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
}
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+ final int numProc = activeProcesses.size();
mAdjSeq++;
if (fullUpdate) {
@@ -971,6 +976,9 @@
mNewNumAServiceProcs = 0;
}
+ // Reset state in all uid records.
+ resetUidRecordsLsp(activeUids);
+
boolean retryCycles = false;
boolean computeClients = fullUpdate || potentialCycles;
@@ -996,8 +1004,9 @@
if (!app.isKilledByAm() && app.getThread() != null) {
state.setProcStateChanged(false);
app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
+ // It won't enter cycle if not computing clients.
computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false,
- computeClients); // It won't enter cycle if not computing clients.
+ computeClients, oomAdjReason, true);
// if any app encountered a cycle, we need to perform an additional loop later
retryCycles |= state.containsCycle();
// Keep the completedAdjSeq to up to date.
@@ -1034,7 +1043,7 @@
final ProcessStateRecord state = app.mState;
if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now,
- true, true)) {
+ true, true, oomAdjReason, true)) {
retryCycles = true;
}
}
@@ -1045,10 +1054,33 @@
assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+
+ if (startProfiling) {
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) {
+ // Reset state in all uid records.
+ for (int i = activeUids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = activeUids.valueAt(i);
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+ }
+ uidRec.reset();
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
+ long now, long nowElapsed, long oldTime) {
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
- boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+ final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
oomAdjReason);
mNumServiceProcs = mNewNumServiceProcs;
@@ -1085,14 +1117,10 @@
Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
}
}
- if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
}
@GuardedBy({"mService", "mProcLock"})
- private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+ protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
if (mConstants.USE_TIERED_CACHED_ADJ) {
final long now = SystemClock.uptimeMillis();
@@ -1413,7 +1441,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
+ protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
if (!app.isKilledByAm() && app.getThread() != null) {
if (app.isolated && app.mServices.numberOfRunningServices() <= 0
&& app.getIsolatedEntryPoint() == null) {
@@ -1442,7 +1470,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
+ protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
// This compares previously set procstate to the current procstate in regards to whether
// or not the app's network access will be blocked. So, this needs to be called before
// we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}.
@@ -1580,7 +1608,7 @@
return true;
}
- private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
+ protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
new ComputeOomAdjWindowCallback();
/** These methods are called inline during computeOomAdjLSP(), on the same thread */
@@ -1719,24 +1747,30 @@
}
@GuardedBy({"mService", "mProcLock"})
- private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
+ protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
- boolean computeClients) {
+ boolean computeClients, int oomAdjReason, boolean couldRecurse) {
final ProcessStateRecord state = app.mState;
- if (mAdjSeq == state.getAdjSeq()) {
- if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
- // This adjustment has already been computed successfully.
- return false;
- } else {
- // The process is being computed, so there is a cycle. We cannot
- // rely on this process's state.
- state.setContainsCycle(true);
- mProcessesInCycle.add(app);
+ if (couldRecurse) {
+ if (mAdjSeq == state.getAdjSeq()) {
+ if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
+ // This adjustment has already been computed successfully.
+ return false;
+ } else {
+ // The process is being computed, so there is a cycle. We cannot
+ // rely on this process's state.
+ state.setContainsCycle(true);
+ mProcessesInCycle.add(app);
- return false;
+ return false;
+ }
}
}
+ int prevAppAdj = getInitialAdj(app);
+ int prevProcState = getInitialProcState(app);
+ int prevCapability = getInitialCapability(app);
+
if (app.getThread() == null) {
state.setAdjSeq(mAdjSeq);
state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
@@ -1745,6 +1779,8 @@
state.setCurRawAdj(CACHED_APP_MAX_ADJ);
state.setCompletedAdjSeq(state.getAdjSeq());
state.setCurCapability(PROCESS_CAPABILITY_NONE);
+ onProcessStateChanged(app, prevProcState);
+ onProcessOomAdjChanged(app, prevAppAdj);
return false;
}
@@ -1753,7 +1789,7 @@
state.setAdjTarget(null);
state.setEmpty(false);
state.setCached(false);
- if (!cycleReEval) {
+ if (!couldRecurse || !cycleReEval) {
// Don't reset this flag when doing cycles re-evaluation.
state.setNoKillOnBgRestrictedAndIdle(false);
// If this UID is currently allowlisted, it should not be frozen.
@@ -1764,9 +1800,6 @@
final int appUid = app.info.uid;
final int logUid = mService.mCurOomAdjUid;
- int prevAppAdj = state.getCurAdj();
- int prevProcState = state.getCurProcState();
- int prevCapability = state.getCurCapability();
final ProcessServiceRecord psr = app.mServices;
if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) {
@@ -1812,6 +1845,8 @@
state.setCurRawProcState(state.getCurProcState());
state.setCurAdj(state.getMaxAdj());
state.setCompletedAdjSeq(state.getAdjSeq());
+ onProcessStateChanged(app, prevProcState);
+ onProcessOomAdjChanged(app, prevAppAdj);
// if curAdj is less than prevAppAdj, then this process was promoted
return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
}
@@ -1825,7 +1860,7 @@
int adj;
int schedGroup;
int procState;
- int capability = cycleReEval ? app.mState.getCurCapability() : 0;
+ int capability = cycleReEval ? getInitialCapability(app) : 0;
boolean foregroundActivities = false;
boolean hasVisibleActivities = false;
@@ -1904,7 +1939,7 @@
// value that the caller wants us to.
adj = cachedAdj;
procState = PROCESS_STATE_CACHED_EMPTY;
- if (!state.containsCycle()) {
+ if (!couldRecurse || !state.containsCycle()) {
state.setCached(true);
state.setEmpty(true);
state.setAdjType("cch-empty");
@@ -2169,8 +2204,10 @@
}
}
- boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp();
- boolean scheduleLikeTopApp = false;
+ state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app));
+
+ state.setScheduleLikeTopApp(false);
+
for (int is = psr.numberOfRunningServices() - 1;
is >= 0 && (adj > FOREGROUND_APP_ADJ
|| schedGroup == SCHED_GROUP_BACKGROUND
@@ -2243,6 +2280,18 @@
}
}
+ if (!couldRecurse) {
+ // We're entering recursive functions below, if we're told it's not a recursive
+ // loop, abort here.
+ continue;
+ }
+
+
+ state.setCurRawAdj(adj);
+ state.setCurRawProcState(procState);
+ state.setCurrentSchedulingGroup(schedGroup);
+ state.setCurCapability(capability);
+
ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
for (int conni = serviceConnections.size() - 1;
conni >= 0 && (adj > FOREGROUND_APP_ADJ
@@ -2263,335 +2312,13 @@
continue;
}
- boolean trackedProcState = false;
+ computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
+ cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
- ProcessRecord client = cr.binding.client;
- if (app.isSdkSandbox && cr.binding.attributedClient != null) {
- // For SDK sandboxes, use the attributed client (eg the app that
- // requested the sandbox)
- client = cr.binding.attributedClient;
- }
- final ProcessStateRecord cstate = client.mState;
- if (computeClients) {
- computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
- cycleReEval, true);
- } else {
- cstate.setCurRawAdj(cstate.getCurAdj());
- cstate.setCurRawProcState(cstate.getCurProcState());
- }
-
- int clientAdj = cstate.getCurRawAdj();
- int clientProcState = cstate.getCurRawProcState();
-
- final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
-
- boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
- || clientProcState <= PROCESS_STATE_BOUND_TOP
- || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
- && !cstate.isBackgroundRestricted());
-
- if (client.mOptRecord.shouldNotFreeze()) {
- // Propagate the shouldNotFreeze flag down the bindings.
- app.mOptRecord.setShouldNotFreeze(true);
- }
-
- // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
- // but, right before actually setting it to the process,
- // we check the final procstate, and remove it if the procsate is below BFGS.
- capability |= getBfslCapabilityFromClient(client);
-
- if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
- if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
- capability |= cstate.getCurCapability();
- }
-
- // If an app has network capability by default
- // (by having procstate <= BFGS), then the apps it binds to will get
- // elevated to a high enough procstate anyway to get network unless they
- // request otherwise, so don't propagate the network capability by default
- // in this case unless they explicitly request it.
- if ((cstate.getCurCapability()
- & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
- if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
- // This is used to grant network access to Expedited Jobs.
- if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
- capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
- }
- } else {
- capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
- }
- }
- if ((cstate.getCurCapability()
- & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
- if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
- // This is used to grant network access to User Initiated Jobs.
- if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
- capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
- }
- }
- }
-
- if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- continue;
- }
-
- if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
- // If the other app is cached for any reason, for purposes here
- // we are going to consider it empty. The specific cached state
- // doesn't propagate except under certain conditions.
- clientProcState = PROCESS_STATE_CACHED_EMPTY;
- }
- String adjType = null;
- if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
- // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
- if (clientAdj < CACHED_APP_MIN_ADJ) {
- app.mOptRecord.setShouldNotFreeze(true);
- }
- // Not doing bind OOM management, so treat
- // this guy more like a started service.
- if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
- // If this process has shown some UI, let it immediately
- // go to the LRU list because it may be pretty heavy with
- // UI stuff. We'll tag it with a label just to help
- // debug and understand what is going on.
- if (adj > clientAdj) {
- adjType = "cch-bound-ui-services";
- }
- state.setCached(false);
- clientAdj = adj;
- clientProcState = procState;
- } else {
- if (now >= (s.lastActivity
- + mConstants.MAX_SERVICE_INACTIVITY)) {
- // This service has not seen activity within
- // recent memory, so allow it to drop to the
- // LRU list if there is no other reason to keep
- // it around. We'll also tag it with a label just
- // to help debug and undertand what is going on.
- if (adj > clientAdj) {
- adjType = "cch-bound-services";
- }
- clientAdj = adj;
- }
- }
- }
- if (adj > clientAdj) {
- // If this process has recently shown UI, and
- // the process that is binding to it is less
- // important than being visible, then we don't
- // care about the binding as much as we care
- // about letting this process get into the LRU
- // list to be killed and restarted if needed for
- // memory.
- if (state.hasShownUi() && !state.getCachedIsHomeProcess()
- && clientAdj > PERCEPTIBLE_APP_ADJ) {
- if (adj >= CACHED_APP_MIN_ADJ) {
- adjType = "cch-bound-ui-services";
- }
- } else {
- int newAdj;
- int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
- if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
- | Context.BIND_IMPORTANT)) {
- if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
- newAdj = clientAdj;
- } else {
- // make this service persistent
- newAdj = PERSISTENT_SERVICE_ADJ;
- schedGroup = SCHED_GROUP_DEFAULT;
- procState = ActivityManager.PROCESS_STATE_PERSISTENT;
- cr.trackProcState(procState, mAdjSeq);
- trackedProcState = true;
- }
- } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
- && clientAdj <= PERCEPTIBLE_APP_ADJ
- && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
- newAdj = PERCEPTIBLE_LOW_APP_ADJ;
- } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
- && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
- && clientAdj < PERCEPTIBLE_APP_ADJ
- && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
- // This is for user-initiated jobs.
- // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
- newAdj = PERCEPTIBLE_APP_ADJ + 1;
- } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
- && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
- && clientAdj < PERCEPTIBLE_APP_ADJ
- && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
- // This is for expedited jobs.
- // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
- // EJ and short-FGS.
- newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
- } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
- && clientAdj < PERCEPTIBLE_APP_ADJ
- && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
- newAdj = PERCEPTIBLE_APP_ADJ;
- } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
- newAdj = clientAdj;
- } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
- && clientAdj <= VISIBLE_APP_ADJ
- && adj > VISIBLE_APP_ADJ) {
- newAdj = VISIBLE_APP_ADJ;
- } else {
- if (adj > VISIBLE_APP_ADJ) {
- // TODO: Is this too limiting for apps bound from TOP?
- newAdj = Math.max(clientAdj, lbAdj);
- } else {
- newAdj = adj;
- }
- }
- if (!cstate.isCached()) {
- state.setCached(false);
- }
- if (adj > newAdj) {
- adj = newAdj;
- state.setCurRawAdj(adj);
- adjType = "service";
- }
- }
- }
- if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
- | Context.BIND_IMPORTANT_BACKGROUND)) {
- // This will treat important bound services identically to
- // the top app, which may behave differently than generic
- // foreground work.
- final int curSchedGroup = cstate.getCurrentSchedulingGroup();
- if (curSchedGroup > schedGroup) {
- if (cr.hasFlag(Context.BIND_IMPORTANT)) {
- schedGroup = curSchedGroup;
- } else {
- schedGroup = SCHED_GROUP_DEFAULT;
- }
- }
- if (clientProcState < PROCESS_STATE_TOP) {
- // Special handling for above-top states (persistent
- // processes). These should not bring the current process
- // into the top state, since they are not on top. Instead
- // give them the best bound state after that.
- if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
- clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
- } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
- clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else if (mService.mWakefulness.get()
- == PowerManagerInternal.WAKEFULNESS_AWAKE
- && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE))
- {
- clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else {
- clientProcState =
- PROCESS_STATE_IMPORTANT_FOREGROUND;
- }
- } else if (clientProcState == PROCESS_STATE_TOP) {
- // Go at most to BOUND_TOP, unless requested to elevate
- // to client's state.
- clientProcState = PROCESS_STATE_BOUND_TOP;
- final boolean enabled = cstate.getCachedCompatChange(
- CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
- if (enabled) {
- if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
- // TOP process passes all capabilities to the service.
- capability |= cstate.getCurCapability();
- } else {
- // TOP process passes no capability to the service.
- }
- } else {
- // TOP process passes all capabilities to the service.
- capability |= cstate.getCurCapability();
- }
- }
- } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
- if (clientProcState <
- PROCESS_STATE_TRANSIENT_BACKGROUND) {
- clientProcState =
- PROCESS_STATE_TRANSIENT_BACKGROUND;
- }
- } else {
- if (clientProcState <
- PROCESS_STATE_IMPORTANT_BACKGROUND) {
- clientProcState =
- PROCESS_STATE_IMPORTANT_BACKGROUND;
- }
- }
-
- if (schedGroup < SCHED_GROUP_TOP_APP
- && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
- && clientIsSystem) {
- schedGroup = SCHED_GROUP_TOP_APP;
- scheduleLikeTopApp = true;
- }
-
- if (!trackedProcState) {
- cr.trackProcState(clientProcState, mAdjSeq);
- }
-
- if (procState > clientProcState) {
- procState = clientProcState;
- state.setCurRawProcState(procState);
- if (adjType == null) {
- adjType = "service";
- }
- }
- if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
- && cr.hasFlag(Context.BIND_SHOWING_UI)) {
- app.setPendingUiClean(true);
- }
- if (adjType != null) {
- state.setAdjType(adjType);
- state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE);
- state.setAdjSource(client);
- state.setAdjSourceProcState(clientProcState);
- state.setAdjTarget(s.instanceName);
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
- + ": " + app + ", due to " + client
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
- }
- } else { // BIND_WAIVE_PRIORITY == true
- // BIND_WAIVE_PRIORITY bindings are special when it comes to the
- // freezer. Processes bound via WPRI are expected to be running,
- // but they are not promoted in the LRU list to keep them out of
- // cached. As a result, they can freeze based on oom_adj alone.
- // Normally, bindToDeath would fire when a cached app would die
- // in the background, but nothing will fire when a running process
- // pings a frozen process. Accordingly, any cached app that is
- // bound by an unfrozen app via a WPRI binding has to remain
- // unfrozen.
- if (clientAdj < CACHED_APP_MIN_ADJ) {
- app.mOptRecord.setShouldNotFreeze(true);
- }
- }
- if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- psr.setTreatLikeActivity(true);
- }
- final ActivityServiceConnectionsHolder a = cr.activity;
- if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
- if (a != null && adj > FOREGROUND_APP_ADJ
- && a.isActivityVisible()) {
- adj = FOREGROUND_APP_ADJ;
- state.setCurRawAdj(adj);
- if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
- if (cr.hasFlag(Context.BIND_IMPORTANT)) {
- schedGroup = SCHED_GROUP_TOP_APP_BOUND;
- } else {
- schedGroup = SCHED_GROUP_DEFAULT;
- }
- }
- state.setCached(false);
- state.setAdjType("service");
- state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE);
- state.setAdjSource(a);
- state.setAdjSourceProcState(procState);
- state.setAdjTarget(s.instanceName);
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise to service w/activity: " + app);
- }
- }
- }
+ adj = state.getCurRawAdj();
+ procState = state.getCurRawProcState();
+ schedGroup = state.getCurrentSchedulingGroup();
+ capability = state.getCurCapability();
}
}
}
@@ -2603,97 +2330,27 @@
|| procState > PROCESS_STATE_TOP);
provi--) {
ContentProviderRecord cpr = ppr.getProviderAt(provi);
- for (int i = cpr.connections.size() - 1;
- i >= 0 && (adj > FOREGROUND_APP_ADJ
- || schedGroup == SCHED_GROUP_BACKGROUND
- || procState > PROCESS_STATE_TOP);
- i--) {
- ContentProviderConnection conn = cpr.connections.get(i);
- ProcessRecord client = conn.client;
- final ProcessStateRecord cstate = client.mState;
- if (client == app) {
- // Being our own client is not interesting.
- continue;
- }
- if (computeClients) {
- computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
- } else {
- cstate.setCurRawAdj(cstate.getCurAdj());
- cstate.setCurRawProcState(cstate.getCurProcState());
- }
+ if (couldRecurse) {
+ // We're entering recursive functions below.
+ state.setCurRawAdj(adj);
+ state.setCurRawProcState(procState);
+ state.setCurrentSchedulingGroup(schedGroup);
+ state.setCurCapability(capability);
- if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- continue;
- }
+ for (int i = cpr.connections.size() - 1;
+ i >= 0 && (adj > FOREGROUND_APP_ADJ
+ || schedGroup == SCHED_GROUP_BACKGROUND
+ || procState > PROCESS_STATE_TOP);
+ i--) {
+ ContentProviderConnection conn = cpr.connections.get(i);
+ ProcessRecord client = conn.client;
+ computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll,
+ cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
- int clientAdj = cstate.getCurRawAdj();
- int clientProcState = cstate.getCurRawProcState();
-
- // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
- // but, right before actually setting it to the process,
- // we check the final procstate, and remove it if the procsate is below BFGS.
- capability |= getBfslCapabilityFromClient(client);
-
- if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
- // If the other app is cached for any reason, for purposes here
- // we are going to consider it empty.
- clientProcState = PROCESS_STATE_CACHED_EMPTY;
- }
- if (client.mOptRecord.shouldNotFreeze()) {
- // Propagate the shouldNotFreeze flag down the bindings.
- app.mOptRecord.setShouldNotFreeze(true);
- }
-
- boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
- || clientProcState <= PROCESS_STATE_BOUND_TOP
- || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
- && !cstate.isBackgroundRestricted());
-
- String adjType = null;
- if (adj > clientAdj) {
- if (state.hasShownUi() && !state.getCachedIsHomeProcess()
- && clientAdj > PERCEPTIBLE_APP_ADJ) {
- adjType = "cch-ui-provider";
- } else {
- adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
- state.setCurRawAdj(adj);
- adjType = "provider";
- }
- state.setCached(state.isCached() & cstate.isCached());
- }
-
- if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
- if (adjType == null) {
- adjType = "provider";
- }
- if (clientProcState == PROCESS_STATE_TOP) {
- clientProcState = PROCESS_STATE_BOUND_TOP;
- } else {
- clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- }
- }
-
- conn.trackProcState(clientProcState, mAdjSeq);
- if (procState > clientProcState) {
- procState = clientProcState;
- state.setCurRawProcState(procState);
- }
- if (cstate.getCurrentSchedulingGroup() > schedGroup) {
- schedGroup = SCHED_GROUP_DEFAULT;
- }
- if (adjType != null) {
- state.setAdjType(adjType);
- state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
- .REASON_PROVIDER_IN_USE);
- state.setAdjSource(client);
- state.setAdjSourceProcState(clientProcState);
- state.setAdjTarget(cpr.name);
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
- + ": " + app + ", due to " + client
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
+ adj = state.getCurRawAdj();
+ procState = state.getCurRawProcState();
+ schedGroup = state.getCurrentSchedulingGroup();
+ capability = state.getCurCapability();
}
}
// If the provider has external (non-framework) process
@@ -2799,7 +2456,7 @@
// restrictions on screen off
if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
&& mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
- && !scheduleLikeTopApp) {
+ && !state.shouldScheduleLikeTopApp()) {
if (schedGroup > SCHED_GROUP_RESTRICTED) {
schedGroup = SCHED_GROUP_RESTRICTED;
}
@@ -2817,6 +2474,7 @@
capability &= ~PROCESS_CAPABILITY_BFSL;
}
+ state.setHasForegroundActivities(foregroundActivities);
if (app.isPendingFinishAttach()) {
// If the app is still starting up. We reset the computations to the
@@ -2834,22 +2492,580 @@
// it when computing the final cached adj later. Note that we don't need to
// worry about this for max adj above, since max adj will always be used to
// keep it out of the cached vaues.
- state.setCurAdj(adj);
state.setCurCapability(capability);
- state.setCurrentSchedulingGroup(schedGroup);
- state.setCurProcState(procState);
- state.setCurRawProcState(procState);
state.updateLastInvisibleTime(hasVisibleActivities);
- state.setHasForegroundActivities(foregroundActivities);
state.setCompletedAdjSeq(mAdjSeq);
- state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted);
+
+ schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup);
+ setIntermediateProcStateLSP(app, procState, prevProcState);
+ setIntermediateSchedGroupLSP(state, schedGroup);
// if curAdj or curProcState improved, then this process was promoted
return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState
|| state.getCurCapability() != prevCapability;
}
- private int getDefaultCapability(ProcessRecord app, int procState) {
+ /**
+ * @return The proposed change to the schedGroup.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+ int schedGroup) {
+ final ProcessStateRecord state = app.mState;
+ state.setCurRawAdj(adj);
+
+ adj = app.mServices.modifyRawOomAdj(adj);
+ if (adj > state.getMaxAdj()) {
+ adj = state.getMaxAdj();
+ if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
+ schedGroup = SCHED_GROUP_DEFAULT;
+ }
+ }
+
+ state.setCurAdj(adj);
+
+ return schedGroup;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+ int prevProcState) {
+ final ProcessStateRecord state = app.mState;
+ state.setCurProcState(procState);
+ state.setCurRawProcState(procState);
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) {
+ // Put bound foreground services in a special sched group for additional
+ // restrictions on screen off
+ if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+ && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+ && !state.shouldScheduleLikeTopApp()) {
+ if (schedGroup > SCHED_GROUP_RESTRICTED) {
+ schedGroup = SCHED_GROUP_RESTRICTED;
+ }
+ }
+
+ state.setCurrentSchedulingGroup(schedGroup);
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+ boolean couldRecurse) {
+ if (app.isPendingFinishAttach()) {
+ // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+ return;
+ }
+
+ final ProcessStateRecord state = app.mState;
+ ProcessStateRecord cstate = client.mState;
+
+ if (couldRecurse) {
+ if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+ // For SDK sandboxes, use the attributed client (eg the app that
+ // requested the sandbox)
+ client = cr.binding.attributedClient;
+ cstate = client.mState;
+ }
+ if (computeClients) {
+ computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+ oomAdjReason, true);
+ } else {
+ cstate.setCurRawAdj(cstate.getCurAdj());
+ cstate.setCurRawProcState(cstate.getCurProcState());
+ }
+ }
+
+ int clientAdj = cstate.getCurRawAdj();
+ int clientProcState = cstate.getCurRawProcState();
+
+ final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
+
+ int adj = state.getCurRawAdj();
+ int procState = state.getCurRawProcState();
+ int schedGroup = state.getCurrentSchedulingGroup();
+ int capability = state.getCurCapability();
+
+ final int prevRawAdj = adj;
+ final int prevProcState = procState;
+ final int prevSchedGroup = schedGroup;
+
+ final int appUid = app.info.uid;
+ final int logUid = mService.mCurOomAdjUid;
+
+ state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+ || cstate.isCurBoundByNonBgRestrictedApp()
+ || clientProcState <= PROCESS_STATE_BOUND_TOP
+ || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+ && !cstate.isBackgroundRestricted()));
+
+ if (client.mOptRecord.shouldNotFreeze()) {
+ // Propagate the shouldNotFreeze flag down the bindings.
+ app.mOptRecord.setShouldNotFreeze(true);
+ }
+
+ boolean trackedProcState = false;
+
+ // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
+ // but, right before actually setting it to the process,
+ // we check the final procstate, and remove it if the procsate is below BFGS.
+ capability |= getBfslCapabilityFromClient(client);
+
+ if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
+ if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+ capability |= cstate.getCurCapability();
+ }
+
+ // If an app has network capability by default
+ // (by having procstate <= BFGS), then the apps it binds to will get
+ // elevated to a high enough procstate anyway to get network unless they
+ // request otherwise, so don't propagate the network capability by default
+ // in this case unless they explicitly request it.
+ if ((cstate.getCurCapability()
+ & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
+ if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ // This is used to grant network access to Expedited Jobs.
+ if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
+ capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+ }
+ } else {
+ capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+ }
+ }
+ if ((cstate.getCurCapability()
+ & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
+ if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // This is used to grant network access to User Initiated Jobs.
+ if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+ capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+ }
+ }
+ }
+
+ if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+ return;
+ }
+
+ if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+ // If the other app is cached for any reason, for purposes here
+ // we are going to consider it empty. The specific cached state
+ // doesn't propagate except under certain conditions.
+ clientProcState = PROCESS_STATE_CACHED_EMPTY;
+ }
+ String adjType = null;
+ if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
+ // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
+ if (clientAdj < CACHED_APP_MIN_ADJ) {
+ app.mOptRecord.setShouldNotFreeze(true);
+ }
+ // Not doing bind OOM management, so treat
+ // this guy more like a started service.
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
+ // If this process has shown some UI, let it immediately
+ // go to the LRU list because it may be pretty heavy with
+ // UI stuff. We'll tag it with a label just to help
+ // debug and understand what is going on.
+ if (adj > clientAdj) {
+ adjType = "cch-bound-ui-services";
+ }
+ state.setCached(false);
+ clientAdj = adj;
+ clientProcState = procState;
+ } else {
+ if (now >= (cr.binding.service.lastActivity
+ + mConstants.MAX_SERVICE_INACTIVITY)) {
+ // This service has not seen activity within
+ // recent memory, so allow it to drop to the
+ // LRU list if there is no other reason to keep
+ // it around. We'll also tag it with a label just
+ // to help debug and undertand what is going on.
+ if (adj > clientAdj) {
+ adjType = "cch-bound-services";
+ }
+ clientAdj = adj;
+ }
+ }
+ }
+ if (adj > clientAdj) {
+ // If this process has recently shown UI, and
+ // the process that is binding to it is less
+ // important than being visible, then we don't
+ // care about the binding as much as we care
+ // about letting this process get into the LRU
+ // list to be killed and restarted if needed for
+ // memory.
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+ && clientAdj > PERCEPTIBLE_APP_ADJ) {
+ if (adj >= CACHED_APP_MIN_ADJ) {
+ adjType = "cch-bound-ui-services";
+ }
+ } else {
+ int newAdj;
+ int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
+ if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
+ | Context.BIND_IMPORTANT)) {
+ if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
+ newAdj = clientAdj;
+ } else {
+ // make this service persistent
+ newAdj = PERSISTENT_SERVICE_ADJ;
+ schedGroup = SCHED_GROUP_DEFAULT;
+ procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+ cr.trackProcState(procState, mAdjSeq);
+ trackedProcState = true;
+ }
+ } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
+ && clientAdj <= PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
+ newAdj = PERCEPTIBLE_LOW_APP_ADJ;
+ } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+ && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
+ && clientAdj < PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+ // This is for user-initiated jobs.
+ // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
+ newAdj = PERCEPTIBLE_APP_ADJ + 1;
+ } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+ && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
+ && clientAdj < PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
+ // This is for expedited jobs.
+ // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
+ // EJ and short-FGS.
+ newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
+ } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
+ && clientAdj < PERCEPTIBLE_APP_ADJ
+ && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+ newAdj = PERCEPTIBLE_APP_ADJ;
+ } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
+ newAdj = clientAdj;
+ } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+ && clientAdj <= VISIBLE_APP_ADJ
+ && adj > VISIBLE_APP_ADJ) {
+ newAdj = VISIBLE_APP_ADJ;
+ } else {
+ if (adj > VISIBLE_APP_ADJ) {
+ // TODO: Is this too limiting for apps bound from TOP?
+ newAdj = Math.max(clientAdj, lbAdj);
+ } else {
+ newAdj = adj;
+ }
+ }
+ if (!cstate.isCached()) {
+ state.setCached(false);
+ }
+ if (adj > newAdj) {
+ adj = newAdj;
+ state.setCurRawAdj(adj);
+ adjType = "service";
+ }
+ }
+ }
+ if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
+ | Context.BIND_IMPORTANT_BACKGROUND)) {
+ // This will treat important bound services identically to
+ // the top app, which may behave differently than generic
+ // foreground work.
+ final int curSchedGroup = cstate.getCurrentSchedulingGroup();
+ if (curSchedGroup > schedGroup) {
+ if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+ schedGroup = curSchedGroup;
+ } else {
+ schedGroup = SCHED_GROUP_DEFAULT;
+ }
+ }
+ if (clientProcState < PROCESS_STATE_TOP) {
+ // Special handling for above-top states (persistent
+ // processes). These should not bring the current process
+ // into the top state, since they are not on top. Instead
+ // give them the best bound state after that.
+ if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+ clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+ } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+ clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ } else if (mService.mWakefulness.get()
+ == PowerManagerInternal.WAKEFULNESS_AWAKE
+ && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
+ clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ } else {
+ clientProcState =
+ PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
+ } else if (clientProcState == PROCESS_STATE_TOP) {
+ // Go at most to BOUND_TOP, unless requested to elevate
+ // to client's state.
+ clientProcState = PROCESS_STATE_BOUND_TOP;
+ final boolean enabled = cstate.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
+ if (enabled) {
+ if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+ // TOP process passes all capabilities to the service.
+ capability |= cstate.getCurCapability();
+ } else {
+ // TOP process passes no capability to the service.
+ }
+ } else {
+ // TOP process passes all capabilities to the service.
+ capability |= cstate.getCurCapability();
+ }
+ }
+ } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
+ if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ clientProcState =
+ PROCESS_STATE_TRANSIENT_BACKGROUND;
+ }
+ } else {
+ if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) {
+ clientProcState =
+ PROCESS_STATE_IMPORTANT_BACKGROUND;
+ }
+ }
+
+ if (schedGroup < SCHED_GROUP_TOP_APP
+ && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
+ && clientIsSystem) {
+ schedGroup = SCHED_GROUP_TOP_APP;
+ state.setScheduleLikeTopApp(true);
+ }
+
+ if (!trackedProcState) {
+ cr.trackProcState(clientProcState, mAdjSeq);
+ }
+
+ if (procState > clientProcState) {
+ procState = clientProcState;
+ state.setCurRawProcState(procState);
+ if (adjType == null) {
+ adjType = "service";
+ }
+ }
+ if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
+ && cr.hasFlag(Context.BIND_SHOWING_UI)) {
+ app.setPendingUiClean(true);
+ }
+ if (adjType != null) {
+ state.setAdjType(adjType);
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE);
+ state.setAdjSource(client);
+ state.setAdjSourceProcState(clientProcState);
+ state.setAdjTarget(cr.binding.service.instanceName);
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ + ": " + app + ", due to " + client
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ }
+ } else { // BIND_WAIVE_PRIORITY == true
+ // BIND_WAIVE_PRIORITY bindings are special when it comes to the
+ // freezer. Processes bound via WPRI are expected to be running,
+ // but they are not promoted in the LRU list to keep them out of
+ // cached. As a result, they can freeze based on oom_adj alone.
+ // Normally, bindToDeath would fire when a cached app would die
+ // in the background, but nothing will fire when a running process
+ // pings a frozen process. Accordingly, any cached app that is
+ // bound by an unfrozen app via a WPRI binding has to remain
+ // unfrozen.
+ if (clientAdj < CACHED_APP_MIN_ADJ) {
+ app.mOptRecord.setShouldNotFreeze(true);
+ }
+ }
+ if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
+ app.mServices.setTreatLikeActivity(true);
+ if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY
+ && procState > PROCESS_STATE_CACHED_ACTIVITY) {
+ // This is a cached process, but somebody wants us to treat it like it has
+ // an activity, okay!
+ procState = PROCESS_STATE_CACHED_ACTIVITY;
+ state.setAdjType("cch-as-act");
+ }
+ }
+ final ActivityServiceConnectionsHolder a = cr.activity;
+ if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
+ if (a != null && adj > FOREGROUND_APP_ADJ
+ && a.isActivityVisible()) {
+ adj = FOREGROUND_APP_ADJ;
+ state.setCurRawAdj(adj);
+ if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
+ if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+ schedGroup = SCHED_GROUP_TOP_APP_BOUND;
+ } else {
+ schedGroup = SCHED_GROUP_DEFAULT;
+ }
+ }
+ state.setCached(false);
+ state.setAdjType("service");
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE);
+ state.setAdjSource(a);
+ state.setAdjSourceProcState(procState);
+ state.setAdjTarget(cr.binding.service.instanceName);
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise to service w/activity: " + app);
+ }
+ }
+ }
+
+ capability |= getDefaultCapability(app, procState);
+
+ // Procstates below BFGS should never have this capability.
+ if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ capability &= ~PROCESS_CAPABILITY_BFSL;
+ }
+
+ if (adj < prevRawAdj) {
+ schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+ }
+ if (procState < prevProcState) {
+ setIntermediateProcStateLSP(app, procState, prevProcState);
+ }
+ if (schedGroup > prevSchedGroup) {
+ setIntermediateSchedGroupLSP(state, schedGroup);
+ }
+ state.setCurCapability(capability);
+
+ state.setEmpty(false);
+ }
+
+ protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+ boolean couldRecurse) {
+ if (app.isPendingFinishAttach()) {
+ // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+ return;
+ }
+
+ final ProcessStateRecord state = app.mState;
+ final ProcessStateRecord cstate = client.mState;
+
+ if (client == app) {
+ // Being our own client is not interesting.
+ return;
+ }
+ if (couldRecurse) {
+ if (computeClients) {
+ computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+ oomAdjReason, true);
+ } else if (couldRecurse) {
+ cstate.setCurRawAdj(cstate.getCurAdj());
+ cstate.setCurRawProcState(cstate.getCurProcState());
+ }
+
+ if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(),
+ cycleReEval)) {
+ return;
+ }
+ }
+
+ int clientAdj = cstate.getCurRawAdj();
+ int clientProcState = cstate.getCurRawProcState();
+
+ int adj = state.getCurRawAdj();
+ int procState = state.getCurRawProcState();
+ int schedGroup = state.getCurrentSchedulingGroup();
+ int capability = state.getCurCapability();
+
+ final int prevRawAdj = adj;
+ final int prevProcState = procState;
+ final int prevSchedGroup = schedGroup;
+
+ final int appUid = app.info.uid;
+ final int logUid = mService.mCurOomAdjUid;
+
+ // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
+ // but, right before actually setting it to the process,
+ // we check the final procstate, and remove it if the procsate is below BFGS.
+ capability |= getBfslCapabilityFromClient(client);
+
+ if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+ // If the other app is cached for any reason, for purposes here
+ // we are going to consider it empty.
+ clientProcState = PROCESS_STATE_CACHED_EMPTY;
+ }
+ if (client.mOptRecord.shouldNotFreeze()) {
+ // Propagate the shouldNotFreeze flag down the bindings.
+ app.mOptRecord.setShouldNotFreeze(true);
+ }
+
+ state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+ || cstate.isCurBoundByNonBgRestrictedApp()
+ || clientProcState <= PROCESS_STATE_BOUND_TOP
+ || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+ && !cstate.isBackgroundRestricted()));
+
+ String adjType = null;
+ if (adj > clientAdj) {
+ if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+ && clientAdj > PERCEPTIBLE_APP_ADJ) {
+ adjType = "cch-ui-provider";
+ } else {
+ adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
+ state.setCurRawAdj(adj);
+ adjType = "provider";
+ }
+ state.setCached(state.isCached() & cstate.isCached());
+ }
+
+ if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (adjType == null) {
+ adjType = "provider";
+ }
+ if (clientProcState == PROCESS_STATE_TOP) {
+ clientProcState = PROCESS_STATE_BOUND_TOP;
+ } else {
+ clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ }
+ }
+
+ conn.trackProcState(clientProcState, mAdjSeq);
+ if (procState > clientProcState) {
+ procState = clientProcState;
+ state.setCurRawProcState(procState);
+ }
+ if (cstate.getCurrentSchedulingGroup() > schedGroup) {
+ schedGroup = SCHED_GROUP_DEFAULT;
+ }
+ if (adjType != null) {
+ state.setAdjType(adjType);
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_PROVIDER_IN_USE);
+ state.setAdjSource(client);
+ state.setAdjSourceProcState(clientProcState);
+ state.setAdjTarget(conn.provider.name);
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ + ": " + app + ", due to " + client
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ }
+
+ // Procstates below BFGS should never have this capability.
+ if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+ capability &= ~PROCESS_CAPABILITY_BFSL;
+ }
+
+ if (adj < prevRawAdj) {
+ schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+ }
+ if (procState < prevProcState) {
+ setIntermediateProcStateLSP(app, procState, prevProcState);
+ }
+ if (schedGroup > prevSchedGroup) {
+ setIntermediateSchedGroupLSP(state, schedGroup);
+ }
+ state.setCurCapability(capability);
+
+ state.setEmpty(false);
+ }
+
+ protected int getDefaultCapability(ProcessRecord app, int procState) {
final int networkCapabilities =
NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState);
final int baseCapabilities;
@@ -2882,7 +3098,7 @@
/**
* @return the BFSL capability from a client (of a service binding or provider).
*/
- int getBfslCapabilityFromClient(ProcessRecord client) {
+ protected int getBfslCapabilityFromClient(ProcessRecord client) {
// Procstates above FGS should always have this flag. We shouldn't need this logic,
// but let's do it just in case.
if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -2967,7 +3183,7 @@
/** Inform the oomadj observer of changes to oomadj. Used by tests. */
@GuardedBy("mService")
- private void reportOomAdjMessageLocked(String tag, String msg) {
+ protected void reportOomAdjMessageLocked(String tag, String msg) {
Slog.d(tag, msg);
synchronized (mService.mOomAdjObserverLock) {
if (mService.mCurOomAdjObserver != null) {
@@ -2983,7 +3199,7 @@
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
@GuardedBy({"mService", "mProcLock"})
- private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
+ protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
long nowElapsed, @OomAdjReason int oomAdjReson) {
boolean success = true;
final ProcessStateRecord state = app.mState;
@@ -3272,6 +3488,8 @@
int initialCapability = PROCESS_CAPABILITY_NONE;
boolean initialCached = true;
final ProcessStateRecord state = app.mState;
+ final int prevProcState = PROCESS_STATE_UNKNOWN;
+ final int prevAdj = UNKNOWN_ADJ;
// If the process has been marked as foreground, it is starting as the top app (with
// Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
if (state.hasForegroundActivities()) {
@@ -3306,6 +3524,9 @@
state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
state.setForcingToImportant(null);
state.setHasShownUi(false);
+
+ onProcessStateChanged(app, prevProcState);
+ onProcessOomAdjChanged(app, prevAdj);
}
// ONLY used for unit testing in OomAdjusterTests.java
@@ -3553,4 +3774,56 @@
}
processes.clear();
}
+
+ @GuardedBy("mService")
+ void onProcessBeginLocked(@NonNull ProcessRecord app) {
+ // Empty, the OomAdjusterModernImpl will have an implementation.
+ }
+
+ @GuardedBy("mService")
+ void onProcessEndLocked(@NonNull ProcessRecord app) {
+ // Empty, the OomAdjusterModernImpl will have an implementation.
+ }
+
+ /**
+ * Called when the process state is changed outside of the OomAdjuster.
+ */
+ @GuardedBy("mService")
+ void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+ // Empty, the OomAdjusterModernImpl will have an implementation.
+ }
+
+ /**
+ * Called when the oom adj is changed outside of the OomAdjuster.
+ */
+ @GuardedBy("mService")
+ void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+ // Empty, the OomAdjusterModernImpl will have an implementation.
+ }
+
+ @VisibleForTesting
+ void resetInternal() {
+ // Empty, the OomAdjusterModernImpl will have an implementation.
+ }
+
+ @GuardedBy("mService")
+ protected int getInitialAdj(@NonNull ProcessRecord app) {
+ return app.mState.getCurAdj();
+ }
+
+ @GuardedBy("mService")
+ protected int getInitialProcState(@NonNull ProcessRecord app) {
+ return app.mState.getCurProcState();
+ }
+
+ @GuardedBy("mService")
+ protected int getInitialCapability(@NonNull ProcessRecord app) {
+ return app.mState.getCurCapability();
+ }
+
+ @GuardedBy("mService")
+ protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+ // The caller will set the initial value in this implementation.
+ return app.mState.isCurBoundByNonBgRestrictedApp();
+ }
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
index 16091d1..da5e12e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.md
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -130,3 +130,28 @@
* Iterate the processes from least important to most important ones.
* A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3.
+## The Modern Implementation
+
+As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong.
+
+The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below:
+
+```
+for all processes in the process list
+ compute the state of each process, but, excluding its clients
+ put each process to the corresponding bucket according to the state value
+done
+
+for each bucket, starting from the top most to the bottom most
+ for each process in the bucket
+ for each process it binds to
+ if the state of the bindee process could be elevated because of the binding; then
+ move the bindee process to the higher bucket
+ fi
+ done
+ done
+done
+```
+
+The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm.
+
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
new file mode 100644
index 0000000..b852ef5
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -0,0 +1,1125 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BACKUP;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+import static android.app.ActivityManager.PROCESS_STATE_HOME;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
+import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
+import static com.android.server.am.ProcessList.HOME_APP_ADJ;
+import static com.android.server.am.ProcessList.NATIVE_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
+import static com.android.server.am.ProcessList.SERVICE_ADJ;
+import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
+import static com.android.server.am.ProcessList.SYSTEM_ADJ;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * A modern implementation of the oom adjuster.
+ */
+public class OomAdjusterModernImpl extends OomAdjuster {
+ static final String TAG = "OomAdjusterModernImpl";
+
+ // The ADJ_SLOT_INVALID is NOT an actual slot.
+ static final int ADJ_SLOT_INVALID = -1;
+ static final int ADJ_SLOT_NATIVE = 0;
+ static final int ADJ_SLOT_SYSTEM = 1;
+ static final int ADJ_SLOT_PERSISTENT_PROC = 2;
+ static final int ADJ_SLOT_PERSISTENT_SERVICE = 3;
+ static final int ADJ_SLOT_FOREGROUND_APP = 4;
+ static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5;
+ static final int ADJ_SLOT_VISIBLE_APP = 6;
+ static final int ADJ_SLOT_PERCEPTIBLE_APP = 7;
+ static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8;
+ static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9;
+ static final int ADJ_SLOT_BACKUP_APP = 10;
+ static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11;
+ static final int ADJ_SLOT_SERVICE = 12;
+ static final int ADJ_SLOT_HOME_APP = 13;
+ static final int ADJ_SLOT_PREVIOUS_APP = 14;
+ static final int ADJ_SLOT_SERVICE_B = 15;
+ static final int ADJ_SLOT_CACHED_APP = 16;
+ static final int ADJ_SLOT_UNKNOWN = 17;
+
+ @IntDef(prefix = { "ADJ_SLOT_" }, value = {
+ ADJ_SLOT_INVALID,
+ ADJ_SLOT_NATIVE,
+ ADJ_SLOT_SYSTEM,
+ ADJ_SLOT_PERSISTENT_PROC,
+ ADJ_SLOT_PERSISTENT_SERVICE,
+ ADJ_SLOT_FOREGROUND_APP,
+ ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP,
+ ADJ_SLOT_VISIBLE_APP,
+ ADJ_SLOT_PERCEPTIBLE_APP,
+ ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP,
+ ADJ_SLOT_PERCEPTIBLE_LOW_APP,
+ ADJ_SLOT_BACKUP_APP,
+ ADJ_SLOT_HEAVY_WEIGHT_APP,
+ ADJ_SLOT_SERVICE,
+ ADJ_SLOT_HOME_APP,
+ ADJ_SLOT_PREVIOUS_APP,
+ ADJ_SLOT_SERVICE_B,
+ ADJ_SLOT_CACHED_APP,
+ ADJ_SLOT_UNKNOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface AdjSlot{}
+
+ static final int[] ADJ_SLOT_VALUES = new int[] {
+ NATIVE_ADJ,
+ SYSTEM_ADJ,
+ PERSISTENT_PROC_ADJ,
+ PERSISTENT_SERVICE_ADJ,
+ FOREGROUND_APP_ADJ,
+ PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ,
+ VISIBLE_APP_ADJ,
+ PERCEPTIBLE_APP_ADJ,
+ PERCEPTIBLE_MEDIUM_APP_ADJ,
+ PERCEPTIBLE_LOW_APP_ADJ,
+ BACKUP_APP_ADJ,
+ HEAVY_WEIGHT_APP_ADJ,
+ SERVICE_ADJ,
+ HOME_APP_ADJ,
+ PREVIOUS_APP_ADJ,
+ SERVICE_B_ADJ,
+ CACHED_APP_MIN_ADJ,
+ UNKNOWN_ADJ,
+ };
+
+ /**
+ * Note: Always use the raw adj to call this API.
+ */
+ static @AdjSlot int adjToSlot(int adj) {
+ if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) {
+ // Conduct a binary search, in most of the cases it'll get a hit.
+ final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj);
+ if (index >= 0) {
+ return index;
+ }
+ // If not found, the returned index above should be (-(insertion point) - 1),
+ // let's return the first slot that's less than the adj value.
+ return -(index + 1) - 1;
+ }
+ return ADJ_SLOT_VALUES.length - 1;
+ }
+
+ static final int[] PROC_STATE_SLOTS = new int[] {
+ PROCESS_STATE_PERSISTENT, // 0
+ PROCESS_STATE_PERSISTENT_UI,
+ PROCESS_STATE_TOP,
+ PROCESS_STATE_BOUND_TOP,
+ PROCESS_STATE_FOREGROUND_SERVICE,
+ PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+ PROCESS_STATE_IMPORTANT_FOREGROUND,
+ PROCESS_STATE_IMPORTANT_BACKGROUND,
+ PROCESS_STATE_TRANSIENT_BACKGROUND,
+ PROCESS_STATE_BACKUP,
+ PROCESS_STATE_SERVICE,
+ PROCESS_STATE_RECEIVER,
+ PROCESS_STATE_TOP_SLEEPING,
+ PROCESS_STATE_HEAVY_WEIGHT,
+ PROCESS_STATE_HOME,
+ PROCESS_STATE_LAST_ACTIVITY,
+ PROCESS_STATE_CACHED_ACTIVITY,
+ PROCESS_STATE_CACHED_ACTIVITY_CLIENT,
+ PROCESS_STATE_CACHED_RECENT,
+ PROCESS_STATE_CACHED_EMPTY,
+ PROCESS_STATE_UNKNOWN, // -1
+ };
+
+ static int processStateToSlot(@ActivityManager.ProcessState int state) {
+ if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) {
+ return state;
+ }
+ return PROC_STATE_SLOTS.length - 1;
+ }
+
+ /**
+ * A container node in the {@link LinkedProcessRecordList},
+ * holding the references to {@link ProcessRecord}.
+ */
+ static class ProcessRecordNode {
+ static final int NODE_TYPE_PROC_STATE = 0;
+ static final int NODE_TYPE_ADJ = 1;
+
+ @IntDef(prefix = { "NODE_TYPE_" }, value = {
+ NODE_TYPE_PROC_STATE,
+ NODE_TYPE_ADJ,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface NodeType {}
+
+ static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1;
+
+ @Nullable ProcessRecordNode mPrev;
+ @Nullable ProcessRecordNode mNext;
+ final @Nullable ProcessRecord mApp;
+
+ ProcessRecordNode(@Nullable ProcessRecord app) {
+ mApp = app;
+ }
+
+ void unlink() {
+ if (mPrev != null) {
+ mPrev.mNext = mNext;
+ }
+ if (mNext != null) {
+ mNext.mPrev = mPrev;
+ }
+ mPrev = mNext = null;
+ }
+
+ boolean isLinked() {
+ return mPrev != null && mNext != null;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("ProcessRecordNode{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(' ');
+ sb.append(mApp);
+ sb.append(' ');
+ sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN);
+ sb.append(' ');
+ sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ);
+ sb.append(' ');
+ sb.append(Integer.toHexString(System.identityHashCode(mPrev)));
+ sb.append(' ');
+ sb.append(Integer.toHexString(System.identityHashCode(mNext)));
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ private class ProcessRecordNodes {
+ private final @ProcessRecordNode.NodeType int mType;
+
+ private final LinkedProcessRecordList[] mProcessRecordNodes;
+ // The last node besides the tail.
+ private final ProcessRecordNode[] mLastNode;
+
+ ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
+ mType = type;
+ mProcessRecordNodes = new LinkedProcessRecordList[size];
+ for (int i = 0; i < size; i++) {
+ mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
+ }
+ mLastNode = new ProcessRecordNode[size];
+ }
+
+ int size() {
+ return mProcessRecordNodes.length;
+ }
+
+ @VisibleForTesting
+ void reset() {
+ for (int i = 0; i < mProcessRecordNodes.length; i++) {
+ mProcessRecordNodes[i].reset();
+ mLastNode[i] = null;
+ }
+ }
+
+ void resetLastNodes() {
+ for (int i = 0; i < mProcessRecordNodes.length; i++) {
+ mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
+ }
+ }
+
+ void setLastNodeToHead(int slot) {
+ mLastNode[slot] = mProcessRecordNodes[slot].HEAD;
+ }
+
+ void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
+ ProcessRecordNode node = mLastNode[slot].mNext;
+ final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
+ while (node != tail) {
+ mTmpOomAdjusterArgs.mApp = node.mApp;
+ // Save the next before calling callback, since that may change the node.mNext.
+ final ProcessRecordNode next = node.mNext;
+ callback.accept(mTmpOomAdjusterArgs);
+ // There are couple of cases:
+ // a) The current node is moved to another slot
+ // - for this case, we'd need to keep using the "next" node.
+ // b) There are one or more new nodes being appended to this slot
+ // - for this case, we'd need to make sure we scan the new node too.
+ // Based on the assumption that case a) is only possible with
+ // the computeInitialOomAdjLSP(), where the movings are for single node only,
+ // we may safely assume that, if the "next" used to be the "tail" here, and it's
+ // now a new tail somewhere else, that's case a); otherwise, it's case b);
+ node = next == tail && node.mNext != null && node.mNext.mNext != null
+ ? node.mNext : next;
+ }
+ }
+
+ int getNumberOfSlots() {
+ return mProcessRecordNodes.length;
+ }
+
+ void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) {
+ final ProcessRecordNode node = app.mLinkedNodes[mType];
+ if (prevSlot != ADJ_SLOT_INVALID) {
+ if (mLastNode[prevSlot] == node) {
+ mLastNode[prevSlot] = node.mPrev;
+ }
+ node.unlink();
+ }
+ mProcessRecordNodes[newSlot].append(node);
+ }
+
+ void moveAllNodesTo(int fromSlot, int toSlot) {
+ final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot];
+ final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot];
+ if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) {
+ fromList.moveTo(toList);
+ mLastNode[fromSlot] = fromList.getLastNodeBeforeTail();
+ }
+ }
+
+ void moveAppToTail(ProcessRecord app) {
+ final ProcessRecordNode node = app.mLinkedNodes[mType];
+ int slot;
+ switch (mType) {
+ case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+ slot = processStateToSlot(app.mState.getCurProcState());
+ if (mLastNode[slot] == node) {
+ mLastNode[slot] = node.mPrev;
+ }
+ mProcessRecordNodes[slot].moveNodeToTail(node);
+ break;
+ case ProcessRecordNode.NODE_TYPE_ADJ:
+ slot = adjToSlot(app.mState.getCurRawAdj());
+ if (mLastNode[slot] == node) {
+ mLastNode[slot] = node.mPrev;
+ }
+ mProcessRecordNodes[slot].moveNodeToTail(node);
+ break;
+ default:
+ return;
+ }
+
+ }
+
+ void reset(int slot) {
+ mProcessRecordNodes[slot].reset();
+ }
+
+ void unlink(@NonNull ProcessRecord app) {
+ final ProcessRecordNode node = app.mLinkedNodes[mType];
+ final int slot = getCurrentSlot(app);
+ if (slot != ADJ_SLOT_INVALID) {
+ if (mLastNode[slot] == node) {
+ mLastNode[slot] = node.mPrev;
+ }
+ }
+ node.unlink();
+ }
+
+ void append(@NonNull ProcessRecord app) {
+ append(app, getCurrentSlot(app));
+ }
+
+ void append(@NonNull ProcessRecord app, int targetSlot) {
+ final ProcessRecordNode node = app.mLinkedNodes[mType];
+ mProcessRecordNodes[targetSlot].append(node);
+ }
+
+ private int getCurrentSlot(@NonNull ProcessRecord app) {
+ switch (mType) {
+ case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+ return processStateToSlot(app.mState.getCurProcState());
+ case ProcessRecordNode.NODE_TYPE_ADJ:
+ return adjToSlot(app.mState.getCurRawAdj());
+ }
+ return ADJ_SLOT_INVALID;
+ }
+
+ String toString(int slot, int logUid) {
+ return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid);
+ }
+
+ /**
+ * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node
+ * while adding an object to it.
+ */
+ private static class LinkedProcessRecordList {
+ // Sentinel head/tail, to make bookkeeping work easier.
+ final ProcessRecordNode HEAD = new ProcessRecordNode(null);
+ final ProcessRecordNode TAIL = new ProcessRecordNode(null);
+ final @ProcessRecordNode.NodeType int mNodeType;
+
+ LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
+ HEAD.mNext = TAIL;
+ TAIL.mPrev = HEAD;
+ mNodeType = nodeType;
+ }
+
+ void append(@NonNull ProcessRecordNode node) {
+ node.mNext = TAIL;
+ node.mPrev = TAIL.mPrev;
+ TAIL.mPrev.mNext = node;
+ TAIL.mPrev = node;
+ }
+
+ void moveTo(@NonNull LinkedProcessRecordList toList) {
+ if (HEAD.mNext != TAIL) {
+ toList.TAIL.mPrev.mNext = HEAD.mNext;
+ HEAD.mNext.mPrev = toList.TAIL.mPrev;
+ toList.TAIL.mPrev = TAIL.mPrev;
+ TAIL.mPrev.mNext = toList.TAIL;
+ HEAD.mNext = TAIL;
+ TAIL.mPrev = HEAD;
+ }
+ }
+
+ void moveNodeToTail(@NonNull ProcessRecordNode node) {
+ node.unlink();
+ append(node);
+ }
+
+ @NonNull ProcessRecordNode getLastNodeBeforeTail() {
+ return TAIL.mPrev;
+ }
+
+ @VisibleForTesting
+ void reset() {
+ HEAD.mNext = TAIL;
+ TAIL.mPrev = HEAD;
+ }
+
+ String toString(int logUid) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("LinkedProcessRecordList{");
+ sb.append(HEAD);
+ sb.append(' ');
+ sb.append(TAIL);
+ sb.append('[');
+ ProcessRecordNode node = HEAD.mNext;
+ while (node != TAIL) {
+ if (node.mApp != null && node.mApp.uid == logUid) {
+ sb.append(node);
+ sb.append(',');
+ }
+ node = node.mNext;
+ }
+ sb.append(']');
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+ }
+
+ /**
+ * A data class for holding the parameters in computing oom adj.
+ */
+ private class OomAdjusterArgs {
+ ProcessRecord mApp;
+ ProcessRecord mTopApp;
+ long mNow;
+ int mCachedAdj;
+ @OomAdjReason int mOomAdjReason;
+ @NonNull ActiveUids mUids;
+ boolean mFullUpdate;
+
+ void update(ProcessRecord topApp, long now, int cachedAdj,
+ @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) {
+ mTopApp = topApp;
+ mNow = now;
+ mCachedAdj = cachedAdj;
+ mOomAdjReason = oomAdjReason;
+ mUids = uids;
+ mFullUpdate = fullUpdate;
+ }
+ }
+
+ OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+ ActiveUids activeUids) {
+ this(service, processList, activeUids, createAdjusterThread());
+ }
+
+ OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+ ActiveUids activeUids, ServiceThread adjusterThread) {
+ super(service, processList, activeUids, adjusterThread);
+ }
+
+ private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
+ ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length);
+ private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes(
+ ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length);
+ private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs();
+
+ void linkProcessRecordToList(@NonNull ProcessRecord app) {
+ mProcessRecordProcStateNodes.append(app);
+ mProcessRecordAdjNodes.append(app);
+ }
+
+ void unlinkProcessRecordFromList(@NonNull ProcessRecord app) {
+ mProcessRecordProcStateNodes.unlink(app);
+ mProcessRecordAdjNodes.unlink(app);
+ }
+
+ @Override
+ @VisibleForTesting
+ void resetInternal() {
+ mProcessRecordProcStateNodes.reset();
+ mProcessRecordAdjNodes.reset();
+ }
+
+ @GuardedBy("mService")
+ @Override
+ void onProcessBeginLocked(@NonNull ProcessRecord app) {
+ // Check one type should be good enough.
+ if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) {
+ for (int i = 0; i < app.mLinkedNodes.length; i++) {
+ app.mLinkedNodes[i] = new ProcessRecordNode(app);
+ }
+ }
+ if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+ linkProcessRecordToList(app);
+ }
+ }
+
+ @GuardedBy("mService")
+ @Override
+ void onProcessEndLocked(@NonNull ProcessRecord app) {
+ if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null
+ && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+ unlinkProcessRecordFromList(app);
+ }
+ }
+
+ @GuardedBy("mService")
+ @Override
+ void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+ updateProcStateSlotIfNecessary(app, prevProcState);
+ }
+
+ @GuardedBy("mService")
+ void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+ updateAdjSlotIfNecessary(app, prevAdj);
+ }
+
+ @GuardedBy("mService")
+ @Override
+ protected int getInitialAdj(@NonNull ProcessRecord app) {
+ return UNKNOWN_ADJ;
+ }
+
+ @GuardedBy("mService")
+ @Override
+ protected int getInitialProcState(@NonNull ProcessRecord app) {
+ return PROCESS_STATE_UNKNOWN;
+ }
+
+ @GuardedBy("mService")
+ @Override
+ protected int getInitialCapability(@NonNull ProcessRecord app) {
+ return 0;
+ }
+
+ @GuardedBy("mService")
+ @Override
+ protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+ return false;
+ }
+
+ private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
+ if (app.mState.getCurRawAdj() != prevRawAdj) {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
+ }
+ }
+
+ private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
+ if (app.mState.getCurProcState() != prevProcState) {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ if (slot != prevSlot) {
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
+ }
+ }
+
+ @Override
+ protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ final ProcessRecord topApp = mService.getTopApp();
+
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
+ mAdjSeq++;
+
+ final ProcessStateRecord state = app.mState;
+ final int oldAdj = state.getCurRawAdj();
+ final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
+ ? oldAdj : UNKNOWN_ADJ;
+
+ final ActiveUids uids = mTmpUidRecords;
+ final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+ final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+
+ uids.clear();
+ targetProcesses.clear();
+ targetProcesses.add(app);
+ reachableProcesses.clear();
+
+ // Find out all reachable processes from this app.
+ collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
+
+ // Copy all of the reachable processes into the target process set.
+ targetProcesses.addAll(reachableProcesses);
+ reachableProcesses.clear();
+
+ final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
+ topApp, targetProcesses, uids, false, now, cachedAdj);
+
+ reachableProcesses.addAll(targetProcesses);
+ assignCachedAdjIfNecessary(reachableProcesses);
+ for (int i = uids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = uids.valueAt(i);
+ uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+ }
+ updateUidsLSP(uids, nowElapsed);
+ for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+ applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+ }
+ targetProcesses.clear();
+ reachableProcesses.clear();
+
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ return result;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ @Override
+ protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
+ boolean startProfiling) {
+ final boolean fullUpdate = processes == null;
+ final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+ ? mProcessList.getLruProcessesLOSP() : processes;
+ ActiveUids activeUids = uids;
+ if (activeUids == null) {
+ final int numUids = mActiveUids.size();
+ activeUids = mTmpUidRecords;
+ activeUids.clear();
+ for (int i = 0; i < numUids; i++) {
+ UidRecord uidRec = mActiveUids.valueAt(i);
+ activeUids.put(uidRec.getUid(), uidRec);
+ }
+ }
+
+ if (startProfiling) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
+ }
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+ final int numProc = activeProcesses.size();
+
+ mAdjSeq++;
+ if (fullUpdate) {
+ mNewNumServiceProcs = 0;
+ mNewNumAServiceProcs = 0;
+ }
+
+ final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+ targetProcesses.clear();
+ if (!fullUpdate) {
+ targetProcesses.addAll(activeProcesses);
+ }
+
+ performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
+ fullUpdate, now, UNKNOWN_ADJ);
+
+ if (fullUpdate) {
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+ } else {
+ activeProcesses.clear();
+ activeProcesses.addAll(targetProcesses);
+ assignCachedAdjIfNecessary(activeProcesses);
+
+ for (int i = activeUids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = activeUids.valueAt(i);
+ uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+ }
+ updateUidsLSP(activeUids, nowElapsed);
+
+ for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+ applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+ }
+
+ activeProcesses.clear();
+ }
+ targetProcesses.clear();
+
+ if (startProfiling) {
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ return;
+ }
+
+ /**
+ * Perform the oom adj update on the given {@code targetProcesses}.
+ *
+ * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
+ * must have called {@link collectReachableProcessesLocked} on it.
+ */
+ private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
+ ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
+ boolean fullUpdate, long now, int cachedAdj) {
+
+ final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
+ clientProcesses.clear();
+
+ // We'll need to collect the upstream processes of the target apps here, because those
+ // processes would potentially impact the procstate/adj via bindings.
+ if (!fullUpdate) {
+ final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses,
+ clientProcesses);
+
+ // If any of its upstream processes are in a cycle,
+ // move them into the candidate targets.
+ if (containsCycle) {
+ // Add all client apps to the target process list.
+ for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+ final ProcessRecord client = clientProcesses.get(i);
+ final UidRecord uidRec = client.getUidRecord();
+ targetProcesses.add(client);
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ }
+ clientProcesses.clear();
+ }
+ for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+ final ProcessRecord app = targetProcesses.valueAt(i);
+ app.mState.resetCachedInfo();
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+ }
+ uidRec.reset();
+ }
+ }
+ } else {
+ final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+ for (int i = 0, size = lru.size(); i < size; i++) {
+ final ProcessRecord app = lru.get(i);
+ app.mState.resetCachedInfo();
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+ }
+ uidRec.reset();
+ }
+ }
+ }
+
+ updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
+ cachedAdj, now, fullUpdate);
+
+ clientProcesses.clear();
+
+ return true;
+ }
+
+ /**
+ * Collect the reversed reachable processes from the given {@code apps}, the result will be
+ * returned in the given {@code processes}, which will <em>NOT</em> include the processes from
+ * the given {@code apps}.
+ */
+ @GuardedBy("mService")
+ private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+ ArrayList<ProcessRecord> clientProcesses) {
+ final ArrayDeque<ProcessRecord> queue = mTmpQueue;
+ queue.clear();
+ clientProcesses.clear();
+ for (int i = 0, size = apps.size(); i < size; i++) {
+ final ProcessRecord app = apps.valueAt(i);
+ app.mState.setReachable(true);
+ app.mState.setReversedReachable(true);
+ queue.offer(app);
+ }
+
+ // Track if any of them reachables could include a cycle
+ boolean containsCycle = false;
+
+ // Scan upstreams of the process record
+ for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) {
+ if (!pr.mState.isReachable()) {
+ // If not in the given initial set of apps, add it.
+ clientProcesses.add(pr);
+ }
+ final ProcessServiceRecord psr = pr.mServices;
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ final ServiceRecord s = psr.getRunningServiceAt(i);
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+ final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+ for (int k = clist.size() - 1; k >= 0; k--) {
+ final ConnectionRecord cr = clist.get(k);
+ final ProcessRecord client = cr.binding.client;
+ containsCycle |= client.mState.isReversedReachable();
+ if (client.mState.isReversedReachable()) {
+ continue;
+ }
+ queue.offer(client);
+ client.mState.setReversedReachable(true);
+ }
+ }
+ }
+ final ProcessProviderRecord ppr = pr.mProviders;
+ for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = ppr.getProviderAt(i);
+ for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+ final ContentProviderConnection conn = cpr.connections.get(j);
+ final ProcessRecord client = conn.client;
+ containsCycle |= client.mState.isReversedReachable();
+ if (client.mState.isReversedReachable()) {
+ continue;
+ }
+ queue.offer(client);
+ client.mState.setReversedReachable(true);
+ }
+ }
+ // If this process is a sandbox itself, also add the app on whose behalf
+ // its running
+ if (pr.isSdkSandbox) {
+ for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
+ ServiceRecord s = psr.getRunningServiceAt(is);
+ ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+ for (int i = clist.size() - 1; i >= 0; i--) {
+ ConnectionRecord cr = clist.get(i);
+ ProcessRecord attributedApp = cr.binding.attributedClient;
+ if (attributedApp == null || attributedApp == pr) {
+ continue;
+ }
+ containsCycle |= attributedApp.mState.isReversedReachable();
+ if (attributedApp.mState.isReversedReachable()) {
+ continue;
+ }
+ queue.offer(attributedApp);
+ attributedApp.mState.setReversedReachable(true);
+ }
+ }
+ }
+ }
+ }
+
+ // Reset the temporary bits.
+ for (int i = clientProcesses.size() - 1; i >= 0; i--) {
+ clientProcesses.get(i).mState.setReversedReachable(false);
+ }
+ for (int i = 0, size = apps.size(); i < size; i++) {
+ final ProcessRecord app = apps.valueAt(i);
+ app.mState.setReachable(false);
+ app.mState.setReversedReachable(false);
+ }
+ return containsCycle;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
+ ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
+ mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
+
+ mProcessRecordProcStateNodes.resetLastNodes();
+ mProcessRecordAdjNodes.resetLastNodes();
+
+ final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
+ final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+
+ final int appUid = !fullUpdate && targetProcesses.size() > 0
+ ? targetProcesses.valueAt(0).uid : -1;
+ final int logUid = mService.mCurOomAdjUid;
+
+ mAdjSeq++;
+ // All apps to be updated will be moved to the lowest slot.
+ if (fullUpdate) {
+ // Move all the process record node to the lowest slot, we'll do recomputation on all of
+ // them. Use the processes from the lru list, because the scanning order matters here.
+ final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
+ for (int i = procStateTarget; i >= 0; i--) {
+ mProcessRecordProcStateNodes.reset(i);
+ // Force the last node to the head since we'll recompute all of them.
+ mProcessRecordProcStateNodes.setLastNodeToHead(i);
+ }
+ // enqueue the targets in the reverse order of the lru list.
+ for (int i = lruList.size() - 1; i >= 0; i--) {
+ mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
+ }
+ // Do the same to the adj nodes.
+ for (int i = adjTarget; i >= 0; i--) {
+ mProcessRecordAdjNodes.reset(i);
+ // Force the last node to the head since we'll recompute all of them.
+ mProcessRecordAdjNodes.setLastNodeToHead(i);
+ }
+ for (int i = lruList.size() - 1; i >= 0; i--) {
+ mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
+ }
+ } else {
+ // Move the target processes to the lowest slot.
+ for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+ final ProcessRecord app = targetProcesses.valueAt(i);
+ final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
+ final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
+ mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
+ mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
+ }
+ // Move the "lastNode" to head to make sure we scan all nodes in this slot.
+ mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
+ mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+ }
+
+ // All apps to be updated have been moved to the lowest slot.
+ // Do an initial pass of the computation.
+ mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
+ this::computeInitialOomAdjLSP);
+
+ if (!fullUpdate) {
+ // We didn't update the client processes with the computeInitialOomAdjLSP
+ // because they don't need to do so. But they'll be playing vital roles in
+ // computing the bindings. So include them into the scan list below.
+ for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+ mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
+ }
+ // We don't update the adj list since we're resetting it below.
+ }
+
+ // Now nodes are set into their slots, without facting in the bindings.
+ // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
+ //
+ // The whole rationale here is that, the bindings from client to host app, won't elevate
+ // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
+ // is a special case here, but client app's raw adj is still no less than the host app's).
+ // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
+ // check its bindings, elevate its host app's slot if necessary.
+ //
+ // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
+ // Because the procstate and adj are not always in sync - there are cases where
+ // the processes with lower proc state could be getting a higher oom adj score.
+ // And because of this, the procstate and adj node lists are basically two priority heaps.
+ //
+ // As the 2nd pass with the adj node lists potentially includes a significant amount of
+ // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
+ // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
+ // gets bumped, we'll only scan those in 2nd pass.
+
+ mProcessRecordAdjNodes.resetLastNodes();
+
+ // 1st pass, scan each slot in the procstate node list.
+ for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+ mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ }
+
+ // 2nd pass, scan each slot in the adj node list.
+ for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+ mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ }
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
+ final ProcessRecord app = args.mApp;
+ final int cachedAdj = args.mCachedAdj;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final int oomAdjReason = args.mOomAdjReason;
+ final ActiveUids uids = args.mUids;
+ final boolean fullUpdate = args.mFullUpdate;
+
+ if (DEBUG_OOM_ADJ) {
+ Slog.i(TAG, "OOM ADJ initial args app=" + app
+ + " cachedAdj=" + cachedAdj
+ + " topApp=" + topApp
+ + " now=" + now
+ + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
+ + " fullUpdate=" + fullUpdate);
+ }
+
+ if (uids != null) {
+ final UidRecord uidRec = app.getUidRecord();
+
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ }
+
+ computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
+ false);
+ }
+
+ /**
+ * @return The proposed change to the schedGroup.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ @Override
+ protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+ int schedGroup) {
+ schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+
+ updateAdjSlotIfNecessary(app, prevRawAppAdj);
+
+ return schedGroup;
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ @Override
+ protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+ int prevProcState) {
+ super.setIntermediateProcStateLSP(app, procState, prevProcState);
+
+ updateProcStateSlotIfNecessary(app, prevProcState);
+ }
+
+ @GuardedBy({"mService", "mProcLock"})
+ private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+ final ProcessRecord app = args.mApp;
+ final int cachedAdj = args.mCachedAdj;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+ final boolean fullUpdate = args.mFullUpdate;
+ final ActiveUids uids = args.mUids;
+
+ final ProcessServiceRecord psr = app.mServices;
+ for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+ ConnectionRecord cr = psr.getConnectionAt(i);
+ ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS)
+ ? cr.binding.service.isolationHostProc : cr.binding.service.app;
+ if (service == null || service == app
+ || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ continue;
+ }
+
+
+ computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+ oomAdjReason, cachedAdj, false);
+ }
+
+ for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
+ final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i);
+ final ProcessRecord service = cr.binding.service.app;
+ if (service == null || service == app
+ || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ continue;
+ }
+
+ computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+ oomAdjReason, cachedAdj, false);
+ }
+
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
+ ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
+ ProcessRecord provider = cpc.provider.proc;
+ if (provider == null || provider == app
+ || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
+ && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+ && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ continue;
+ }
+
+ computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
+ oomAdjReason, cachedAdj, false);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f532122c1..7037fec 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE;
import static java.util.Objects.requireNonNull;
@@ -63,6 +64,7 @@
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.Zygote;
import com.android.server.FgThread;
+import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
@@ -434,6 +436,8 @@
*/
volatile boolean mSkipProcessGroupCreation;
+ final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
long startUptime, long startElapsedTime) {
this.mStartUid = startUid;
@@ -1114,6 +1118,7 @@
mState.onCleanupApplicationRecordLSP();
mServices.onCleanupApplicationRecordLocked();
mReceivers.onCleanupApplicationRecordLocked();
+ mService.mOomAdjuster.onProcessEndLocked(this);
return mProviders.onCleanupApplicationRecordLocked(allowRestart);
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 7ff6d11..a165e88 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -19,6 +19,7 @@
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ServiceInfo;
@@ -134,6 +135,11 @@
private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
/**
+ * All ConnectionRecord this process holds indirectly to SDK sandbox processes.
+ */
+ private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections;
+
+ /**
* A set of UIDs of all bound clients.
*/
private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
@@ -490,13 +496,18 @@
void addConnection(ConnectionRecord connection) {
mConnections.add(connection);
+ addSdkSandboxConnectionIfNecessary(connection);
}
void removeConnection(ConnectionRecord connection) {
mConnections.remove(connection);
+ removeSdkSandboxConnectionIfNecessary(connection);
}
void removeAllConnections() {
+ for (int i = 0, size = mConnections.size(); i < size; i++) {
+ removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i));
+ }
mConnections.clear();
}
@@ -508,6 +519,39 @@
return mConnections.size();
}
+ private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+ final ProcessRecord attributedClient = connection.binding.attributedClient;
+ if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+ if (attributedClient.mServices.mSdkSandboxConnections == null) {
+ attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>();
+ }
+ attributedClient.mServices.mSdkSandboxConnections.add(connection);
+ }
+ }
+
+ private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+ final ProcessRecord attributedClient = connection.binding.attributedClient;
+ if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+ if (attributedClient.mServices.mSdkSandboxConnections == null) {
+ attributedClient.mServices.mSdkSandboxConnections.remove(connection);
+ }
+ }
+ }
+
+ void removeAllSdkSandboxConnections() {
+ if (mSdkSandboxConnections != null) {
+ mSdkSandboxConnections.clear();
+ }
+ }
+
+ ConnectionRecord getSdkSandboxConnectionAt(int index) {
+ return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null;
+ }
+
+ int numberOfSdkSandboxConnections() {
+ return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0;
+ }
+
void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) {
mBoundClientUids.add(clientUid);
mApp.getWindowProcessController()
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index db341d2..a9c388c 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -378,6 +378,12 @@
private boolean mReachable;
/**
+ * Whether or not this process is reversed reachable from given process.
+ */
+ @GuardedBy("mService")
+ private boolean mReversedReachable;
+
+ /**
* The most recent time when the last visible activity within this process became invisible.
*
* <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
@@ -454,6 +460,9 @@
@GuardedBy("mService")
private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ @GuardedBy("mService")
+ private boolean mScheduleLikeTopApp = false;
+
ProcessStateRecord(ProcessRecord app) {
mApp = app;
mService = app.mService;
@@ -614,9 +623,11 @@
void forceProcessStateUpTo(int newState) {
if (mRepProcState > newState) {
synchronized (mProcLock) {
+ final int prevProcState = mRepProcState;
setReportedProcState(newState);
setCurProcState(newState);
setCurRawProcState(newState);
+ mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState);
}
}
}
@@ -985,6 +996,16 @@
}
@GuardedBy("mService")
+ boolean isReversedReachable() {
+ return mReversedReachable;
+ }
+
+ @GuardedBy("mService")
+ void setReversedReachable(boolean reversedReachable) {
+ mReversedReachable = reversedReachable;
+ }
+
+ @GuardedBy("mService")
void resetCachedInfo() {
mCachedHasActivities = VALUE_INVALID;
mCachedIsHeavyWeight = VALUE_INVALID;
@@ -1134,6 +1155,16 @@
return mCachedSchedGroup;
}
+ @GuardedBy("mService")
+ boolean shouldScheduleLikeTopApp() {
+ return mScheduleLikeTopApp;
+ }
+
+ @GuardedBy("mService")
+ void setScheduleLikeTopApp(boolean scheduleLikeTopApp) {
+ mScheduleLikeTopApp = scheduleLikeTopApp;
+ }
+
@GuardedBy(anyOf = {"mService", "mProcLock"})
public String makeAdjReason() {
if (mAdjSource != null || mAdjTarget != null) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c6d6122..80d14a2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -346,6 +346,9 @@
if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
}
+ Slog.v(TAG, String.format(
+ "Game loading power mode %s (game state change isLoading=%b)",
+ isLoading ? "ON" : "OFF", isLoading));
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
if (isLoading) {
int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -369,6 +372,7 @@
break;
}
case CANCEL_GAME_LOADING_MODE: {
+ Slog.v(TAG, "Game loading power mode OFF (loading boost ended)");
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
break;
}
@@ -1279,6 +1283,7 @@
// instruction.
mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
} else {
+ Slog.v(TAG, "Game loading power mode ON (loading boost on game start)");
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, true);
}
@@ -1555,6 +1560,10 @@
}
}
}, new IntentFilter(Intent.ACTION_SHUTDOWN));
+ Slog.v(TAG, "Game loading power mode OFF (game manager service start/restart)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
+ Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
+ mPowerManagerInternal.setPowerMode(Mode.GAME, false);
}
private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 5819ff0..5c1897d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -185,8 +185,7 @@
* True if {@link #mWakeLock} is open for acquisition. It is set to false after the client is
* unregistered.
*/
- @GuardedBy("mWakeLock")
- private boolean mIsWakelockUsable = true;
+ private AtomicBoolean mIsWakelockUsable = new AtomicBoolean(true);
/*
* Internal interface used to invoke client callbacks.
@@ -529,7 +528,7 @@
@VisibleForTesting
boolean isWakelockUsable() {
synchronized (mWakeLock) {
- return mIsWakelockUsable;
+ return mIsWakelockUsable.get();
}
}
@@ -1103,10 +1102,8 @@
private void acquireWakeLock() {
Binder.withCleanCallingIdentity(
() -> {
- synchronized (mWakeLock) {
- if (mIsWakelockUsable) {
- mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
- }
+ if (mIsWakelockUsable.get()) {
+ mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
}
});
}
@@ -1119,13 +1116,11 @@
private void releaseWakeLock() {
Binder.withCleanCallingIdentity(
() -> {
- synchronized (mWakeLock) {
- if (mWakeLock.isHeld()) {
- try {
- mWakeLock.release();
- } catch (RuntimeException e) {
- Log.e(TAG, "Releasing the wakelock fails - ", e);
- }
+ if (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Releasing the wakelock fails - ", e);
}
}
});
@@ -1139,18 +1134,16 @@
private void releaseWakeLockOnExit() {
Binder.withCleanCallingIdentity(
() -> {
- synchronized (mWakeLock) {
- mIsWakelockUsable = false;
- while (mWakeLock.isHeld()) {
- try {
- mWakeLock.release();
- } catch (RuntimeException e) {
- Log.e(
- TAG,
- "Releasing the wakelock for all acquisitions fails - ",
- e);
- break;
- }
+ mIsWakelockUsable.set(false);
+ while (mWakeLock.isHeld()) {
+ try {
+ mWakeLock.release();
+ } catch (RuntimeException e) {
+ Log.e(
+ TAG,
+ "Releasing the wakelock for all acquisitions fails - ",
+ e);
+ break;
}
}
});
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 09f373f..35c6120 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -466,8 +466,9 @@
// TODO(271471342): Implement
}
- public byte[] getUuid() {
- return UUID;
+ public byte[] getUuid() throws RemoteException {
+ //TODO(b/247124878): return the UUID defined in this file when the API is put in use
+ throw new RemoteException("This API is not implemented yet.");
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index ec0d985..77a60289 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -123,7 +123,7 @@
* @param userId The uid of the user whose profile has been unlocked.
* @param credentialType The type of credential as defined in {@code LockPatternUtils}
* @param credential The credential, encoded as a byte array
- * @param credentialUpdated signals weather credentials were updated.
+ * @param credentialUpdated indicates credentials change.
* @param platformKeyManager platform key manager
* @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
*/
@@ -143,7 +143,7 @@
mRecoverableKeyStoreDb = recoverableKeyStoreDb;
mUserId = userId;
mCredentialType = credentialType;
- mCredential = credential;
+ mCredential = credential != null ? Arrays.copyOf(credential, credential.length) : null;
mCredentialUpdated = credentialUpdated;
mPlatformKeyManager = platformKeyManager;
mRecoverySnapshotStorage = snapshotStorage;
@@ -160,6 +160,10 @@
}
} catch (Exception e) {
Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+ } finally {
+ if (mCredential != null) {
+ Arrays.fill(mCredential, (byte) 0); // no longer needed.
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index b5c0417..1ed829e4 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -32,6 +32,7 @@
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Pair;
import android.util.Slog;
@@ -421,9 +422,19 @@
} else {
candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
}
+ // When we have only a single result belonging to a different user space, we check
+ // if filtering is configured on cross-profile intent resolution for the originating user.
+ // Accordingly, we modify the intent to reflect the content-owner as the originating user,
+ // preventing the resolving user to be assumed the same, when activity is started.
+
+ // In case more than one result is present, the resolver sheet is opened which takes care of
+ // cross user access.
+ if (candidates.size() == 1 && !UserHandle.of(userId).equals(candidates.get(0).userHandle)
+ && isNoFilteringPropertyConfiguredForUser(userId)) {
+ intent.prepareToLeaveUser(userId);
+ }
return new QueryIntentActivitiesResult(sortResult, addInstant, candidates);
}
-
/**
* It filters and combines results from current and cross profile based on domain priority.
* @param computer {@link Computer} instance
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 80e07f4..10cd51a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1886,7 +1886,14 @@
}
public void onSessionFinished(final PackageInstallerSession session, boolean success) {
- mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
+ if (success) {
+ // There is a timing issue here, if the callback opens the session again in
+ // notifySessionFinished() immediately, the session may not be removed from
+ // the mSession. But to avoid adding unknown latency, only notifying failures
+ // are moved to the last of posted runnable, notifying success cases are
+ // still kept here.
+ mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
+ }
mInstallHandler.post(new Runnable() {
@Override
@@ -1915,6 +1922,10 @@
mSettingsWriteRequest.runNow();
}
+ if (!success) {
+ mCallbacks.notifySessionFinished(
+ session.sessionId, session.userId, success);
+ }
}
});
}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 710e0b7..dd434fbe 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -37,6 +37,7 @@
import android.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentProvider;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -1927,11 +1928,32 @@
}
if (shortcut.getIcon() != null) {
ShortcutInfo.validateIcon(shortcut.getIcon());
+ validateIconURI(shortcut);
}
shortcut.replaceFlags(shortcut.getFlags() & ShortcutInfo.FLAG_LONG_LIVED);
}
+ // Validates the calling process has permission to access shortcut icon's image uri
+ private void validateIconURI(@NonNull final ShortcutInfo si) {
+ final int callingUid = injectBinderCallingUid();
+ final Icon icon = si.getIcon();
+ if (icon == null) {
+ // There's no icon in this shortcut, nothing to validate here.
+ return;
+ }
+ int iconType = icon.getType();
+ if (iconType != Icon.TYPE_URI && iconType != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+ // The icon is not URI-based, nothing to validate.
+ return;
+ }
+ final Uri uri = icon.getUri();
+ mUriGrantsManagerInternal.checkGrantUriPermission(callingUid, si.getPackage(),
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(callingUid)));
+ }
+
private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
fixUpIncomingShortcutInfo(shortcut, forUpdate, /*forPinRequest=*/ false);
}
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index f0bf1ea8..d0c346a 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -334,7 +334,10 @@
ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
cancellationReason,
durationMs,
- 0); // deprecated, used to be durationIncludingSleepMs
+ 0, // deprecated, used to be durationIncludingSleepMs
+ 0, // optimizedPackagesCount
+ 0, // packagesDependingOnBootClasspathCount
+ 0); // totalPackagesCount
}
}
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b01a89e..7897195 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -72,6 +72,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -93,7 +94,6 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiFunction;
/**
* Manages all permissions and handles permissions related tasks.
@@ -233,11 +233,11 @@
}
if (checkPermissionDelegate == null) {
- return mPermissionManagerServiceImpl.checkPermission(
- packageName, permissionName, userId);
+ return mPermissionManagerServiceImpl.checkPermission(packageName, permissionName,
+ deviceId, userId);
}
- return checkPermissionDelegate.checkPermission(packageName, permissionName, userId,
- mPermissionManagerServiceImpl::checkPermission);
+ return checkPermissionDelegate.checkPermission(packageName, permissionName,
+ deviceId, userId, mPermissionManagerServiceImpl::checkPermission);
}
@Override
@@ -254,10 +254,10 @@
}
if (checkPermissionDelegate == null) {
- return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName);
+ return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId);
}
return checkPermissionDelegate.checkUidPermission(uid, permissionName,
- mPermissionManagerServiceImpl::checkUidPermission);
+ deviceId, mPermissionManagerServiceImpl::checkUidPermission);
}
@Override
@@ -511,14 +511,14 @@
public int getPermissionFlags(String packageName, String permissionName, int deviceId,
int userId) {
return mPermissionManagerServiceImpl
- .getPermissionFlags(packageName, permissionName, userId);
+ .getPermissionFlags(packageName, permissionName, deviceId, userId);
}
@Override
public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
- flagValues, checkAdjustPolicyFlagPermission, userId);
+ flagValues, checkAdjustPolicyFlagPermission, deviceId, userId);
}
@Override
@@ -560,14 +560,15 @@
@Override
public void grantRuntimePermission(String packageName, String permissionName, int deviceId,
int userId) {
- mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
+ mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName,
+ deviceId, userId);
}
@Override
public void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
int userId, String reason) {
mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName,
- userId, reason);
+ deviceId, userId, reason);
}
@Override
@@ -580,14 +581,14 @@
public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
int deviceId, int userId) {
return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
- permissionName, userId);
+ permissionName, deviceId, userId);
}
@Override
public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
int deviceId, int userId) {
- return mPermissionManagerServiceImpl
- .isPermissionRevokedByPolicy(packageName, permissionName, userId);
+ return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName,
+ permissionName, deviceId, userId);
}
@Override
@@ -868,6 +869,7 @@
*
* @param packageName the name of the package to be checked
* @param permissionName the name of the permission to be checked
+ * @param deviceId The device ID
* @param userId the user ID
* @param superImpl the original implementation that can be delegated to
* @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
@@ -876,20 +878,21 @@
* @see android.content.pm.PackageManager#checkPermission(String, String)
*/
int checkPermission(@NonNull String packageName, @NonNull String permissionName,
- @UserIdInt int userId,
- @NonNull TriFunction<String, String, Integer, Integer> superImpl);
+ int deviceId, @UserIdInt int userId,
+ @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl);
/**
* Check whether the given UID has been granted the specified permission.
*
* @param uid the UID to be checked
* @param permissionName the name of the permission to be checked
+ * @param deviceId The device ID
* @param superImpl the original implementation that can be delegated to
* @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
* the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
*/
- int checkUidPermission(int uid, @NonNull String permissionName,
- BiFunction<Integer, String, Integer> superImpl);
+ int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
+ TriFunction<Integer, String, Integer, Integer> superImpl);
/**
* @return list of delegated permissions
@@ -918,31 +921,32 @@
@Override
public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
- int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) {
+ int deviceId, int userId,
+ @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl) {
if (mDelegatedPackageName.equals(packageName)
&& isDelegatedPermission(permissionName)) {
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply("com.android.shell", permissionName, userId);
+ return superImpl.apply("com.android.shell", permissionName, deviceId, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- return superImpl.apply(packageName, permissionName, userId);
+ return superImpl.apply(packageName, permissionName, deviceId, userId);
}
@Override
- public int checkUidPermission(int uid, @NonNull String permissionName,
- @NonNull BiFunction<Integer, String, Integer> superImpl) {
+ public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
+ @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) {
if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) {
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(Process.SHELL_UID, permissionName);
+ return superImpl.apply(Process.SHELL_UID, permissionName, deviceId);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
- return superImpl.apply(uid, permissionName);
+ return superImpl.apply(uid, permissionName, deviceId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 4353c57..6764e08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -681,7 +681,7 @@
}
@Override
- public int getPermissionFlags(String packageName, String permName, int userId) {
+ public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
final int callingUid = Binder.getCallingUid();
return getPermissionFlagsInternal(packageName, permName, callingUid, userId);
}
@@ -724,7 +724,7 @@
@Override
public void updatePermissionFlags(String packageName, String permName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
final int callingUid = Binder.getCallingUid();
boolean overridePolicy = false;
@@ -908,8 +908,12 @@
}
}
+ private int checkPermission(String pkgName, String permName, int userId) {
+ return checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT, userId);
+ }
+
@Override
- public int checkPermission(String pkgName, String permName, int userId) {
+ public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
if (!mUserManagerInt.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
@@ -975,8 +979,12 @@
return true;
}
+ private int checkUidPermission(int uid, String permName) {
+ return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT);
+ }
+
@Override
- public int checkUidPermission(int uid, String permName) {
+ public int checkUidPermission(int uid, String permName, int deviceId) {
final int userId = UserHandle.getUserId(uid);
if (!mUserManagerInt.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
@@ -1295,7 +1303,8 @@
}
@Override
- public void grantRuntimePermission(String packageName, String permName, final int userId) {
+ public void grantRuntimePermission(String packageName, String permName, int deviceId,
+ int userId) {
final int callingUid = Binder.getCallingUid();
final boolean overridePolicy =
checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
@@ -1468,11 +1477,11 @@
}
@Override
- public void revokeRuntimePermission(String packageName, String permName, int userId,
- String reason) {
+ public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+ int userId, String reason) {
final int callingUid = Binder.getCallingUid();
final boolean overridePolicy =
- checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
+ checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, deviceId)
== PackageManager.PERMISSION_GRANTED;
revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId,
@@ -1859,7 +1868,7 @@
@Override
public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
- @UserIdInt int userId) {
+ int deviceId, @UserIdInt int userId) {
final int callingUid = Binder.getCallingUid();
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingPermission(
@@ -1922,7 +1931,8 @@
}
@Override
- public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+ public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+ int userId) {
if (UserHandle.getCallingUserId() != userId) {
mContext.enforceCallingPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -2059,8 +2069,8 @@
continue;
}
boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(),
- permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED
- | FLAG_PERMISSION_POLICY_FIXED)) != 0;
+ permInfo.name, Context.DEVICE_ID_DEFAULT, userId) & (
+ FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED)) != 0;
if (isSystemOrPolicyFixed) {
continue;
}
@@ -2226,7 +2236,8 @@
for (final int userId : userIds) {
final int permissionState = checkPermission(packageName, permName,
userId);
- final int flags = getPermissionFlags(packageName, permName, userId);
+ final int flags = getPermissionFlags(packageName, permName,
+ Context.DEVICE_ID_DEFAULT, userId);
final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
| FLAG_PERMISSION_POLICY_FIXED
| FLAG_PERMISSION_GRANTED_BY_DEFAULT
@@ -5122,8 +5133,7 @@
@NonNull
@Override
- public Set<String> getGrantedPermissions(@NonNull String packageName,
- @UserIdInt int userId) {
+ public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
Objects.requireNonNull(packageName, "packageName");
Preconditions.checkArgumentNonNegative(userId, "userId");
return getGrantedPermissionsInternal(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 128f847..2d824aa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -25,7 +25,6 @@
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
import android.permission.IOnPermissionsChangeListener;
-import android.permission.PermissionManager;
import android.permission.PermissionManagerInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -137,14 +136,16 @@
void removePermission(String permName);
/**
- * Gets the state flags associated with a permission.
+ * Gets the permission state flags associated with a permission.
*
* @param packageName the package name for which to get the flags
* @param permName the permission for which to get the flags
+ * @param deviceId The device for which to get the flags
* @param userId the user for which to get permission flags
* @return the permission flags
*/
- int getPermissionFlags(String packageName, String permName, int userId);
+ int getPermissionFlags(String packageName, String permName, int deviceId,
+ @UserIdInt int userId);
/**
* Updates the flags associated with a permission by replacing the flags in the specified mask
@@ -154,10 +155,11 @@
* @param permName The permission for which to update the flags
* @param flagMask The flags which to replace
* @param flagValues The flags with which to replace
+ * @param deviceId The device for which to update the permission flags
* @param userId The user for which to update the permission flags
*/
- void updatePermissionFlags(String packageName, String permName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+ void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues,
+ boolean checkAdjustPolicyFlagPermission, int deviceId, @UserIdInt int userId);
/**
* Update the permission flags for all packages and runtime permissions of a user in order
@@ -291,11 +293,13 @@
*
* @param packageName the package to which to grant the permission
* @param permName the permission name to grant
+ * @param deviceId the device for which to grant the permission
* @param userId the user for which to grant the permission
*
- * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String)
+ * @see #revokeRuntimePermission(String, String, int, int, String)
*/
- void grantRuntimePermission(String packageName, String permName, int userId);
+ void grantRuntimePermission(String packageName, String permName, int deviceId,
+ @UserIdInt int userId);
/**
* Revoke a runtime permission that was previously granted by
@@ -310,13 +314,14 @@
*
* @param packageName the package from which to revoke the permission
* @param permName the permission name to revoke
+ * @param deviceId the device for which to revoke the permission
* @param userId the user for which to revoke the permission
* @param reason the reason for the revoke, or {@code null} for unspecified
*
- * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+ * @see #grantRuntimePermission(String, String, int, int)
*/
- void revokeRuntimePermission(String packageName, String permName, int userId,
- String reason);
+ void revokeRuntimePermission(String packageName, String permName, int deviceId,
+ @UserIdInt int userId, String reason);
/**
* Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
@@ -333,24 +338,29 @@
* does not clearly communicate to the user what would be the benefit from grating this
* permission.
*
+ * @param packageName the package name
* @param permName a permission your app wants to request
+ * @param deviceId the device for which to check the permission
+ * @param userId the user for which to check the permission
* @return whether you can show permission rationale UI
*/
boolean shouldShowRequestPermissionRationale(String packageName, String permName,
- @UserIdInt int userId);
+ int deviceId, @UserIdInt int userId);
/**
- * Checks whether a particular permissions has been revoked for a package by policy. Typically
+ * Checks whether a particular permission has been revoked for a package by policy. Typically,
* the device owner or the profile owner may apply such a policy. The user cannot grant policy
* revoked permissions, hence the only way for an app to get such a permission is by a policy
* change.
*
* @param packageName the name of the package you are checking against
* @param permName the name of the permission you are checking for
- *
+ * @param deviceId the device for which you are checking the permission
+ * @param userId the device for which you are checking the permission
* @return whether the permission is restricted by policy
*/
- boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId);
+ boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+ @UserIdInt int userId);
/**
* Get set of permissions that have been split into more granular or dependent permissions.
@@ -373,14 +383,25 @@
List<SplitPermissionInfoParcelable> getSplitPermissions();
/**
- * TODO:theianchen add doc describing this is the old checkPermissionImpl
+ * Check whether a permission is granted or not to a package.
+ *
+ * @param pkgName package name
+ * @param permName permission name
+ * @param deviceId device ID
+ * @param userId user ID
+ * @return permission result {@link PackageManager.PermissionResult}
*/
- int checkPermission(String pkgName, String permName, int userId);
+ int checkPermission(String pkgName, String permName, int deviceId, @UserIdInt int userId);
/**
- * TODO:theianchen add doc describing this is the old checkUidPermissionImpl
+ * Check whether a permission is granted or not to an UID.
+ *
+ * @param uid UID
+ * @param permName permission name
+ * @param deviceId device ID
+ * @return permission result {@link PackageManager.PermissionResult}
*/
- int checkUidPermission(int uid, String permName);
+ int checkUidPermission(int uid, String permName, int deviceId);
/**
* Get all the package names requesting app op permissions.
@@ -400,15 +421,11 @@
@UserIdInt int userId);
/**
- * Reset the runtime permission state changes for a package.
+ * Reset the runtime permission state changes for a package for all devices.
*
* TODO(zhanghai): Turn this into package change callback?
- *
- * @param pkg the package
- * @param userId the user ID
*/
- void resetRuntimePermissions(@NonNull AndroidPackage pkg,
- @UserIdInt int userId);
+ void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId);
/**
* Reset the runtime permission state changes for all packages in a user.
@@ -449,8 +466,8 @@
/**
* Get all the permissions granted to a package.
*
- * @param packageName the name of the package
- * @param userId the user ID
+ * @param packageName package name
+ * @param userId user ID
* @return the names of the granted permissions
*/
@NonNull
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index 7f98e21..dacb8c6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -120,21 +120,21 @@
}
@Override
- public int getPermissionFlags(String packageName, String permName, int userId) {
+ public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = "
- + permName + ", userId = " + userId + ")");
- return mService.getPermissionFlags(packageName, permName, userId);
+ + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+ return mService.getPermissionFlags(packageName, permName, deviceId, userId);
}
@Override
public void updatePermissionFlags(String packageName, String permName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = "
+ permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues
+ ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission
- + ", userId = " + userId + ")");
+ + ", deviceId = " + deviceId + ", userId = " + userId + ")");
mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
- checkAdjustPolicyFlagPermission, userId);
+ checkAdjustPolicyFlagPermission, deviceId, userId);
}
@Override
@@ -182,18 +182,20 @@
}
@Override
- public void grantRuntimePermission(String packageName, String permName, int userId) {
+ public void grantRuntimePermission(String packageName, String permName, int deviceId,
+ int userId) {
Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = "
- + permName + ", userId = " + userId + ")");
- mService.grantRuntimePermission(packageName, permName, userId);
+ + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+ mService.grantRuntimePermission(packageName, permName, deviceId, userId);
}
@Override
- public void revokeRuntimePermission(String packageName, String permName, int userId,
- String reason) {
+ public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+ int userId, String reason) {
Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = "
- + permName + ", userId = " + userId + ", reason = " + reason + ")");
- mService.revokeRuntimePermission(packageName, permName, userId, reason);
+ + permName + ", deviceId = " + deviceId + ", userId = " + userId
+ + ", reason = " + reason + ")");
+ mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
}
@Override
@@ -205,17 +207,20 @@
@Override
public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
- int userId) {
+ int deviceId, int userId) {
Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName
- + ", permName = " + permName + ", userId = " + userId + ")");
- return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
+ + ", permName = " + permName + ", deviceId = " + deviceId
+ + ", userId = " + userId + ")");
+ return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId,
+ userId);
}
@Override
- public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+ public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+ int userId) {
Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = "
- + permName + ", userId = " + userId + ")");
- return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
+ + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+ return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
}
@Override
@@ -225,16 +230,17 @@
}
@Override
- public int checkPermission(String pkgName, String permName, int userId) {
+ public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName
- + ", userId = " + userId + ")");
- return mService.checkPermission(pkgName, permName, userId);
+ + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+ return mService.checkPermission(pkgName, permName, deviceId, userId);
}
@Override
- public int checkUidPermission(int uid, String permName) {
- Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + ")");
- return mService.checkUidPermission(uid, permName);
+ public int checkUidPermission(int uid, String permName, int deviceId) {
+ Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName
+ + ", deviceId = " + deviceId + ")");
+ return mService.checkUidPermission(uid, permName, deviceId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index d4c6d42..35d165b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -153,9 +153,10 @@
}
@Override
- public int getPermissionFlags(String packageName, String permName, int userId) {
- int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId);
- int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId);
+ public int getPermissionFlags(String packageName, String permName, int deviceId,
+ @UserIdInt int userId) {
+ int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
+ int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
if (!Objects.equals(oldVal, newVal)) {
signalImplDifference("getPermissionFlags");
@@ -165,11 +166,12 @@
@Override
public void updatePermissionFlags(String packageName, String permName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId,
+ @UserIdInt int userId) {
mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
- checkAdjustPolicyFlagPermission, userId);
+ checkAdjustPolicyFlagPermission, deviceId, userId);
mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
- checkAdjustPolicyFlagPermission, userId);
+ checkAdjustPolicyFlagPermission, deviceId, userId);
}
@Override
@@ -234,16 +236,17 @@
}
@Override
- public void grantRuntimePermission(String packageName, String permName, int userId) {
- mOldImplementation.grantRuntimePermission(packageName, permName, userId);
- mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+ public void grantRuntimePermission(String packageName, String permName, int deviceId,
+ @UserIdInt int userId) {
+ mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
+ mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
}
@Override
- public void revokeRuntimePermission(String packageName, String permName, int userId,
- String reason) {
- mOldImplementation.grantRuntimePermission(packageName, permName, userId);
- mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+ public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+ @UserIdInt int userId, String reason) {
+ mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
+ mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
}
@Override
@@ -255,11 +258,11 @@
@Override
public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
- int userId) {
- boolean oldVal = mOldImplementation
- .shouldShowRequestPermissionRationale(packageName, permName, userId);
- boolean newVal = mNewImplementation
- .shouldShowRequestPermissionRationale(packageName, permName, userId);
+ int deviceId, @UserIdInt int userId) {
+ boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName,
+ permName, deviceId, userId);
+ boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName,
+ permName, deviceId, userId);
if (!Objects.equals(oldVal, newVal)) {
signalImplDifference("shouldShowRequestPermissionRationale");
@@ -268,11 +271,12 @@
}
@Override
- public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
- boolean oldVal = mOldImplementation
- .isPermissionRevokedByPolicy(packageName, permName, userId);
+ public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+ @UserIdInt int userId) {
+ boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName,
+ deviceId, userId);
boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName,
- userId);
+ deviceId, userId);
if (!Objects.equals(oldVal, newVal)) {
signalImplDifference("isPermissionRevokedByPolicy");
@@ -292,9 +296,9 @@
}
@Override
- public int checkPermission(String pkgName, String permName, int userId) {
- int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId);
- int newVal = mNewImplementation.checkPermission(pkgName, permName, userId);
+ public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
+ int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId);
+ int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId);
if (!Objects.equals(oldVal, newVal)) {
signalImplDifference("checkPermission");
@@ -303,9 +307,9 @@
}
@Override
- public int checkUidPermission(int uid, String permName) {
- int oldVal = mOldImplementation.checkUidPermission(uid, permName);
- int newVal = mNewImplementation.checkUidPermission(uid, permName);
+ public int checkUidPermission(int uid, String permName, int deviceId) {
+ int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId);
+ int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId);
if (!Objects.equals(oldVal, newVal)) {
signalImplDifference("checkUidPermission");
@@ -372,7 +376,7 @@
@NonNull
@Override
- public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) {
+ public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId);
Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index 4e72fae..cbeede0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -158,10 +158,10 @@
}
@Override
- public int getPermissionFlags(String packageName, String permName, int userId) {
+ public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags");
try {
- return mService.getPermissionFlags(packageName, permName, userId);
+ return mService.getPermissionFlags(packageName, permName, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
@@ -169,12 +169,12 @@
@Override
public void updatePermissionFlags(String packageName, String permName, int flagMask,
- int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+ int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags");
try {
mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
- checkAdjustPolicyFlagPermission, userId);
+ checkAdjustPolicyFlagPermission, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
@@ -253,23 +253,24 @@
}
@Override
- public void grantRuntimePermission(String packageName, String permName, int userId) {
+ public void grantRuntimePermission(String packageName, String permName, int deviceId,
+ int userId) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission");
try {
- mService.grantRuntimePermission(packageName, permName, userId);
+ mService.grantRuntimePermission(packageName, permName, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
}
@Override
- public void revokeRuntimePermission(String packageName, String permName, int userId,
- String reason) {
+ public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+ int userId, String reason) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission");
try {
- mService.revokeRuntimePermission(packageName, permName, userId, reason);
+ mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
} finally {
Trace.traceEnd(TRACE_TAG);
}
@@ -288,22 +289,24 @@
@Override
public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
- int userId) {
+ int deviceId, int userId) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale");
try {
- return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
+ return mService.shouldShowRequestPermissionRationale(
+ packageName, permName, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
}
@Override
- public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+ public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+ int userId) {
Trace.traceBegin(TRACE_TAG,
"TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy");
try {
- return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
+ return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
@@ -321,20 +324,20 @@
}
@Override
- public int checkPermission(String pkgName, String permName, int userId) {
+ public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission");
try {
- return mService.checkPermission(pkgName, permName, userId);
+ return mService.checkPermission(pkgName, permName, deviceId, userId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
}
@Override
- public int checkUidPermission(int uid, String permName) {
+ public int checkUidPermission(int uid, String permName, int deviceId) {
Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission");
try {
- return mService.checkUidPermission(uid, permName);
+ return mService.checkUidPermission(uid, permName, deviceId);
} finally {
Trace.traceEnd(TRACE_TAG);
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7f86f1d..faf132e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2656,7 +2656,19 @@
}
}
- public void updateSettings() {
+ private void updateSettings() {
+ updateSettings(null);
+ }
+
+ /**
+ * Update provider Setting values on a given {@code handler}, or synchronously if {@code null}
+ * is passed for handler.
+ */
+ void updateSettings(Handler handler) {
+ if (handler != null) {
+ handler.post(() -> updateSettings(null));
+ return;
+ }
ContentResolver resolver = mContext.getContentResolver();
boolean updateRotation = false;
synchronized (mLock) {
@@ -4499,6 +4511,7 @@
} else {
sleepRelease(event.getEventTime());
}
+ sendSystemKeyToStatusBarAsync(event);
break;
}
@@ -4509,6 +4522,7 @@
if (!down) {
mPowerManagerInternal.setUserInactiveOverrideFromWindowManager();
}
+ sendSystemKeyToStatusBarAsync(event);
break;
}
@@ -5583,12 +5597,7 @@
mDefaultDisplayRotation.updateOrientationListener();
synchronized (mLock) {
mSystemReady = true;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- updateSettings();
- }
- });
+ updateSettings(mHandler);
// If this happens, for whatever reason, systemReady came later than systemBooted.
// And keyguard should be already bound from systemBooted
if (mSystemBooted) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 19ee554..f36ecf7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -155,6 +155,12 @@
// ID of the current user.
@GuardedBy("mLock")
private int mCurrentUserId = UserHandle.USER_SYSTEM;
+ @GuardedBy("mLock")
+ // ID of the current input displayed on the screen.
+ private String mCurrentInputId = null;
+ @GuardedBy("mLock")
+ // SessionState of the currently active TIS session.
+ private SessionState mCurrentSessionState = null;
// IDs of the running profiles. Their parent user ID should be mCurrentUserId.
@GuardedBy("mLock")
private final Set<Integer> mRunningProfiles = new HashSet<>();
@@ -884,6 +890,10 @@
sessionState.session = null;
}
}
+ logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
+ mCurrentInputId, sessionState);
+ mCurrentInputId = null;
+ mCurrentSessionState = null;
removeSessionStateLocked(sessionToken, userId);
return sessionState;
}
@@ -1065,6 +1075,7 @@
Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
return;
}
+ boolean currentCecTvInputInfoUpdated = isCurrentCecTvInputInfoUpdate(userState, inputInfo);
inputState.info = inputInfo;
inputState.uid = getInputUid(inputInfo);
ServiceState serviceState = userState.serviceStateMap.get(inputInfo.getComponent());
@@ -1073,6 +1084,12 @@
mTvInputHardwareManager.updateInputInfo(inputInfo);
}
+ if (currentCecTvInputInfoUpdated) {
+ logExternalInputEvent(
+ FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED,
+ mCurrentInputId, mCurrentSessionState);
+ }
+
int n = userState.mCallbacks.beginBroadcast();
for (int i = 0; i < n; ++i) {
try {
@@ -1085,6 +1102,29 @@
}
@GuardedBy("mLock")
+ private boolean isCurrentCecTvInputInfoUpdate(UserState userState, TvInputInfo newInputInfo) {
+ if (newInputInfo == null || newInputInfo.getId() == null
+ || !newInputInfo.getId().equals(mCurrentInputId)) {
+ return false;
+ }
+ if (newInputInfo.getHdmiDeviceInfo() == null
+ || !newInputInfo.getHdmiDeviceInfo().isCecDevice()) {
+ return false;
+ }
+ TvInputState inputState = userState.inputMap.get(mCurrentInputId);
+ if (inputState == null || inputState.info == null) {
+ return false;
+ }
+ if (inputState.info.getHdmiDeviceInfo() == null
+ || !inputState.info.getHdmiDeviceInfo().isCecDevice()) {
+ return false;
+ }
+ int newVendorId = newInputInfo.getHdmiDeviceInfo().getVendorId(),
+ currentVendorId = inputState.info.getHdmiDeviceInfo().getVendorId();
+ return newVendorId != currentVendorId;
+ }
+
+ @GuardedBy("mLock")
private void setStateLocked(String inputId, int state, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
TvInputState inputState = userState.inputMap.get(inputId);
@@ -1101,6 +1141,24 @@
return;
}
if (oldState != state) {
+ if (inputId.equals(mCurrentInputId)) {
+ logExternalInputEvent(
+ FrameworkStatsLog
+ .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED,
+ mCurrentInputId, mCurrentSessionState);
+ } else if (mCurrentInputId != null) {
+ TvInputInfo currentInputInfo = userState.inputMap.get(mCurrentInputId).info;
+ if (currentInputInfo != null && currentInputInfo.getHdmiDeviceInfo() != null
+ && inputId.equals(currentInputInfo.getParentId())) {
+ logExternalInputEvent(
+ FrameworkStatsLog
+ .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED,
+ inputId, mCurrentSessionState);
+ if (state == INPUT_STATE_CONNECTED_STANDBY) {
+ mCurrentInputId = currentInputInfo.getParentId();
+ }
+ }
+ }
notifyInputStateChangedLocked(userState, inputId, state, null);
}
}
@@ -1764,10 +1822,18 @@
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
userState);
+ if (mCurrentInputId == null
+ || !mCurrentInputId.equals(sessionState.inputId)) {
+ mCurrentInputId = sessionState.inputId;
+ logExternalInputEvent(
+ FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+ sessionState.inputId, sessionState);
+ }
if (!sessionState.isCurrent
|| !Objects.equals(sessionState.currentChannel, channelUri)) {
sessionState.isCurrent = true;
sessionState.currentChannel = channelUri;
+ mCurrentSessionState = sessionState;
notifyCurrentChannelInfosUpdatedLocked(userState);
}
if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
@@ -2990,6 +3056,30 @@
hdmiPort);
}
+ private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
+ UserState userState = getOrCreateUserStateLocked(sessionState.userId);
+ TvInputState tvInputState = userState.inputMap.get(inputId);
+ TvInputInfo tvInputInfo = tvInputState.info;
+ int inputState = tvInputState.state;
+ int inputType = tvInputInfo.getType();
+ // For non-CEC input, the value of vendorId is 0.
+ int vendorId = 0;
+ // For non-HDMI input, the value of hdmiPort is 0.
+ int hdmiPort = 0;
+ String tifSessionId = sessionState.sessionId;
+
+ if (tvInputInfo.getType() == TvInputInfo.TYPE_HDMI) {
+ HdmiDeviceInfo hdmiDeviceInfo = tvInputInfo.getHdmiDeviceInfo();
+ if (hdmiDeviceInfo != null) {
+ vendorId = hdmiDeviceInfo.getVendorId();
+ hdmiPort = hdmiDeviceInfo.getPortId();
+ }
+ }
+
+ FrameworkStatsLog.write(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT, eventType, inputState,
+ inputType, vendorId, hdmiPort, tifSessionId);
+ }
+
private static final class UserState {
// A mapping from the TV input id to its TvInputState.
private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -3353,6 +3443,16 @@
synchronized (mLock) {
mTvInputHardwareManager.addHdmiInput(id, inputInfo);
addHardwareInputLocked(inputInfo);
+ // catch the use case when a CEC device is unplugged from
+ // an HDMI port, then plugged in to the same HDMI port.
+ if (mCurrentInputId != null && mCurrentSessionState != null
+ && mCurrentInputId.equals(inputInfo.getParentId())
+ && inputInfo.getId().equals(mCurrentSessionState.inputId)) {
+ logExternalInputEvent(
+ FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+ inputInfo.getId(), mCurrentSessionState);
+ mCurrentInputId = inputInfo.getId();
+ }
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3455,6 +3555,8 @@
UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
mSessionState.isCurrent = true;
mSessionState.currentChannel = channelUri;
+ mCurrentSessionState = mSessionState;
+ mCurrentInputId = mSessionState.inputId;
notifyCurrentChannelInfosUpdatedLocked(userState);
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 48d477c..111e075a 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -30,7 +30,7 @@
* each frequency.
*
* <p>The {@link VibratorInfo.FrequencyProfile} is only applicable to PWLE compositions. This
- * adapter is only applied to {@link RampSegment} and leaves all other segments unchanged.
+ * adapter is only applied to {@link RampSegment} and all other segments will remain unchanged.
*/
final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAdapter {
@@ -41,13 +41,13 @@
for (int i = 0; i < segmentCount; i++) {
VibrationEffectSegment segment = segments.get(i);
if (segment instanceof RampSegment) {
- segments.set(i, apply((RampSegment) segment, info));
+ segments.set(i, adaptToVibrator(info, (RampSegment) segment));
}
}
return repeatIndex;
}
- private RampSegment apply(RampSegment segment, VibratorInfo info) {
+ private RampSegment adaptToVibrator(VibratorInfo info, RampSegment segment) {
float clampedStartFrequency = clampFrequency(info, segment.getStartFrequencyHz());
float clampedEndFrequency = clampFrequency(info, segment.getEndFrequencyHz());
return new RampSegment(
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index 41649fa..98309cd 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -53,9 +53,14 @@
mSegmentAdapters = Arrays.asList(
// TODO(b/167947076): add filter that removes unsupported primitives
// TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+ // Convert segments based on device capabilities
new RampToStepAdapter(settings.getRampStepDuration()),
new StepToRampAdapter(),
+ // Add extra ramp down segments as needed
new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()),
+ // Split segments based on their duration and device supported limits
+ new SplitSegmentsAdapter(),
+ // Clip amplitudes and frequencies of final segments based on device bandwidth curve
new ClippingAmplitudeAndFrequencyAdapter()
);
mAvailableVibrators = vibrators;
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index 9e248cd..fc19e27 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -28,9 +28,11 @@
import java.util.List;
/**
- * Adapter that converts ramp segments that to a sequence of fixed step segments.
+ * Adapter that converts ramp segments to a sequence of fixed step segments.
*
- * <p>This leaves the list unchanged if the device has compose PWLE capability.
+ * <p>This change preserves the frequency parameters by interpolating the ramp values.
+ *
+ * <p>The segments will not be changed if the device has {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
*/
final class RampToStepAdapter implements VibrationSegmentsAdapter {
@@ -53,7 +55,7 @@
if (!(segment instanceof RampSegment)) {
continue;
}
- List<StepSegment> steps = apply(info, (RampSegment) segment);
+ List<StepSegment> steps = convertRampToSteps(info, (RampSegment) segment);
segments.remove(i);
segments.addAll(i, steps);
int addedSegments = steps.size() - 1;
@@ -66,7 +68,7 @@
return repeatIndex;
}
- private List<StepSegment> apply(VibratorInfo info, RampSegment ramp) {
+ private List<StepSegment> convertRampToSteps(VibratorInfo info, RampSegment ramp) {
if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
// Amplitude is the same, so return a single step to simulate this ramp.
return Arrays.asList(
diff --git a/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java
new file mode 100644
index 0000000..347db35
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.vibrator;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter that splits segments with longer duration than the device capabilities.
+ *
+ * <p>This transformation replaces large {@link RampSegment} entries by a sequence of smaller
+ * ramp segments that starts and ends at the same amplitudes/frequencies, interpolating the
+ * intermediate values.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
+ */
+final class SplitSegmentsAdapter implements VibrationSegmentsAdapter {
+
+ @Override
+ public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+ int repeatIndex) {
+ if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+ // The vibrator does not have PWLE capability, so keep the segments unchanged.
+ return repeatIndex;
+ }
+ int maxRampDuration = info.getPwlePrimitiveDurationMax();
+ if (maxRampDuration <= 0) {
+ // No limit set to PWLE primitive duration.
+ return repeatIndex;
+ }
+
+ int segmentCount = segments.size();
+ for (int i = 0; i < segmentCount; i++) {
+ if (!(segments.get(i) instanceof RampSegment)) {
+ continue;
+ }
+ RampSegment ramp = (RampSegment) segments.get(i);
+ int splits = ((int) ramp.getDuration() + maxRampDuration - 1) / maxRampDuration;
+ if (splits <= 1) {
+ continue;
+ }
+ segments.remove(i);
+ segments.addAll(i, splitRampSegment(info, ramp, splits));
+ int addedSegments = splits - 1;
+ if (repeatIndex > i) {
+ repeatIndex += addedSegments;
+ }
+ i += addedSegments;
+ segmentCount += addedSegments;
+ }
+
+ return repeatIndex;
+ }
+
+ private static List<RampSegment> splitRampSegment(VibratorInfo info, RampSegment ramp,
+ int splits) {
+ List<RampSegment> ramps = new ArrayList<>(splits);
+ // Fill zero frequency values with the device resonant frequency before interpolating.
+ float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz());
+ float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz());
+ long splitDuration = ramp.getDuration() / splits;
+ float previousAmplitude = ramp.getStartAmplitude();
+ float previousFrequencyHz = startFrequencyHz;
+ long accumulatedDuration = 0;
+
+ for (int i = 1; i < splits; i++) {
+ accumulatedDuration += splitDuration;
+ float durationRatio = (float) accumulatedDuration / ramp.getDuration();
+ float interpolatedFrequency =
+ MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
+ float interpolatedAmplitude =
+ MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio);
+ RampSegment rampSplit = new RampSegment(
+ previousAmplitude, interpolatedAmplitude,
+ previousFrequencyHz, interpolatedFrequency,
+ (int) splitDuration);
+ ramps.add(rampSplit);
+ previousAmplitude = rampSplit.getEndAmplitude();
+ previousFrequencyHz = rampSplit.getEndFrequencyHz();
+ }
+
+ ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequencyHz,
+ endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration)));
+
+ return ramps;
+ }
+
+ private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
+ if (Float.isNaN(info.getResonantFrequencyHz())) {
+ return frequencyHz;
+ }
+ return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index d86ee78..6616f10 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -21,17 +21,17 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
-import android.util.MathUtils;
-import java.util.ArrayList;
import java.util.List;
/**
* Adapter that converts step segments that should be handled as PWLEs to ramp segments.
*
- * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
- * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
- * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
+ * <p>Each replaced step will be represented by a ramp with same start and end
+ * amplitudes/frequencies, which can then be converted to PWLE compositions.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
*/
final class StepToRampAdapter implements VibrationSegmentsAdapter {
@@ -42,12 +42,6 @@
// The vibrator does not have PWLE capability, so keep the segments unchanged.
return repeatIndex;
}
- convertStepsToRamps(info, segments);
- repeatIndex = splitLongRampSegments(info, segments, repeatIndex);
- return repeatIndex;
- }
-
- private void convertStepsToRamps(VibratorInfo info, List<VibrationEffectSegment> segments) {
int segmentCount = segments.size();
// Convert steps that require frequency control to ramps.
for (int i = 0; i < segmentCount; i++) {
@@ -68,40 +62,6 @@
}
}
}
- }
-
- /**
- * Split {@link RampSegment} entries that have duration longer than {@link
- * VibratorInfo#getPwlePrimitiveDurationMax()}.
- */
- private int splitLongRampSegments(VibratorInfo info, List<VibrationEffectSegment> segments,
- int repeatIndex) {
- int maxDuration = info.getPwlePrimitiveDurationMax();
- if (maxDuration <= 0) {
- // No limit set to PWLE primitive duration.
- return repeatIndex;
- }
-
- int segmentCount = segments.size();
- for (int i = 0; i < segmentCount; i++) {
- if (!(segments.get(i) instanceof RampSegment)) {
- continue;
- }
- RampSegment ramp = (RampSegment) segments.get(i);
- int splits = ((int) ramp.getDuration() + maxDuration - 1) / maxDuration;
- if (splits <= 1) {
- continue;
- }
- segments.remove(i);
- segments.addAll(i, splitRampSegment(info, ramp, splits));
- int addedSegments = splits - 1;
- if (repeatIndex > i) {
- repeatIndex += addedSegments;
- }
- i += addedSegments;
- segmentCount += addedSegments;
- }
-
return repeatIndex;
}
@@ -111,38 +71,6 @@
frequencyHz, frequencyHz, (int) segment.getDuration());
}
- private static List<RampSegment> splitRampSegment(VibratorInfo info, RampSegment ramp,
- int splits) {
- List<RampSegment> ramps = new ArrayList<>(splits);
- float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz());
- float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz());
- long splitDuration = ramp.getDuration() / splits;
- float previousAmplitude = ramp.getStartAmplitude();
- float previousFrequency = startFrequencyHz;
- long accumulatedDuration = 0;
-
- for (int i = 1; i < splits; i++) {
- accumulatedDuration += splitDuration;
- float durationRatio = (float) accumulatedDuration / ramp.getDuration();
- float interpolatedFrequency =
- MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
- float interpolatedAmplitude =
- MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio);
- RampSegment rampSplit = new RampSegment(
- previousAmplitude, interpolatedAmplitude,
- previousFrequency, interpolatedFrequency,
- (int) splitDuration);
- ramps.add(rampSplit);
- previousAmplitude = rampSplit.getEndAmplitude();
- previousFrequency = rampSplit.getEndFrequencyHz();
- }
-
- ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequency,
- endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration)));
-
- return ramps;
- }
-
private static boolean isStep(VibrationEffectSegment segment) {
return segment instanceof StepSegment;
}
diff --git a/services/core/java/com/android/server/vibrator/TEST_MAPPING b/services/core/java/com/android/server/vibrator/TEST_MAPPING
index f0a7e47..92b327d 100644
--- a/services/core/java/com/android/server/vibrator/TEST_MAPPING
+++ b/services/core/java/com/android/server/vibrator/TEST_MAPPING
@@ -1,21 +1,10 @@
{
- "presubmit": [
+ "imports": [
{
- "name": "FrameworksVibratorServicesTests",
- "options": [
- {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
- {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
- ],
- "postsubmit": [
+ "path": "frameworks/base/services/tests/vibrator"
+ },
{
- "name": "FrameworksVibratorServicesTests",
- "options": [
- {"exclude-annotation": "org.junit.Ignore"}
- ]
+ "path": "cts/tests/vibrator"
}
]
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 4f7f13e..fed6e7e 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.AudioAttributes;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
@@ -27,8 +28,10 @@
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
+import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -39,7 +42,9 @@
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final SimpleDateFormat DEBUG_DATE_FORMAT =
+ private static final SimpleDateFormat DEBUG_TIME_FORMAT =
+ new SimpleDateFormat("HH:mm:ss.SSS");
+ private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -146,10 +151,10 @@
@Override
public String toString() {
return "CallerInfo{"
- + " attrs=" + attrs
- + ", uid=" + uid
- + ", displayId=" + displayId
+ + " uid=" + uid
+ ", opPkg=" + opPkg
+ + ", displayId=" + displayId
+ + ", attrs=" + attrs
+ ", reason=" + reason
+ '}';
}
@@ -203,14 +208,17 @@
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfo {
- private final long mCreateTime;
+ final long mCreateTime;
+ final CallerInfo mCallerInfo;
+ @Nullable
+ final CombinedVibration mPlayedEffect;
+
private final long mStartTime;
private final long mEndTime;
private final long mDurationMs;
- @Nullable private final CombinedVibration mOriginalEffect;
- @Nullable private final CombinedVibration mPlayedEffect;
+ @Nullable
+ private final CombinedVibration mOriginalEffect;
private final float mScale;
- private final CallerInfo mCallerInfo;
private final Status mStatus;
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
@@ -230,10 +238,10 @@
@Override
public String toString() {
- return "createTime: " + DEBUG_DATE_FORMAT.format(new Date(mCreateTime))
- + ", startTime: " + DEBUG_DATE_FORMAT.format(new Date(mStartTime))
+ return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
+ + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
+ ", endTime: "
- + (mEndTime == 0 ? null : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
+ + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", playedEffect: " + mPlayedEffect
@@ -242,8 +250,56 @@
+ ", callerInfo: " + mCallerInfo;
}
+ /**
+ * Write this info in a compact way into given {@link PrintWriter}.
+ *
+ * <p>This is used by dumpsys to log multiple vibration records in single lines that are
+ * easy to skim through by the sorted created time.
+ */
+ void dumpCompact(IndentingPrintWriter pw) {
+ boolean isExternalVibration = mPlayedEffect == null;
+ String timingsStr = String.format(Locale.ROOT,
+ "%s | %8s | %20s | duration: %5dms | start: %12s | end: %10s",
+ DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ isExternalVibration ? "external" : "effect",
+ mStatus.name().toLowerCase(Locale.ROOT),
+ mDurationMs,
+ mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
+ mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+ String callerInfoStr = String.format(Locale.ROOT,
+ " | %s (uid=%d, displayId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
+ mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.displayId,
+ mCallerInfo.attrs.usageToString(),
+ AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()),
+ Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+ mCallerInfo.reason);
+ String effectStr = String.format(Locale.ROOT,
+ " | played: %s | original: %s | scale: %.2f",
+ mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
+ mOriginalEffect == null ? null : mOriginalEffect.toDebugString(),
+ mScale);
+ pw.println(timingsStr + callerInfoStr + effectStr);
+ }
+
+ /** Write this info into given {@link PrintWriter}. */
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Vibration:");
+ pw.increaseIndent();
+ pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+ pw.println("durationMs = " + mDurationMs);
+ pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
+ pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
+ pw.println("endTime = "
+ + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+ pw.println("playedEffect = " + mPlayedEffect);
+ pw.println("originalEffect = " + mOriginalEffect);
+ pw.println("scale = " + String.format(Locale.ROOT, "%.2f", mScale));
+ pw.println("callerInfo = " + mCallerInfo);
+ pw.decreaseIndent();
+ }
+
/** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
- public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ void dump(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(VibrationProto.START_TIME, mStartTime);
proto.write(VibrationProto.END_TIME, mEndTime);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index dbd6bf4..db8a9ae 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -54,6 +54,7 @@
import android.os.Vibrator.VibrationIntensity;
import android.os.vibrator.VibrationConfig;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -65,6 +66,7 @@
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -601,8 +603,32 @@
}
}
+ /** Write current settings into given {@link PrintWriter}. */
+ void dump(IndentingPrintWriter pw) {
+ pw.println("VibrationSettings:");
+ pw.increaseIndent();
+ pw.println("vibrateOn = " + mVibrateOn);
+ pw.println("vibrateInputDevices = " + mVibrateInputDevices);
+ pw.println("batterySaverMode = " + mBatterySaverMode);
+ pw.println("VibrationIntensities:");
+
+ pw.increaseIndent();
+ for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
+ int usage = mCurrentVibrationIntensities.keyAt(i);
+ int intensity = mCurrentVibrationIntensities.valueAt(i);
+ pw.println(VibrationAttributes.usageToString(usage) + " = "
+ + intensityToString(intensity)
+ + ", default: " + intensityToString(getDefaultIntensity(usage)));
+ }
+ pw.decreaseIndent();
+
+ mVibrationConfig.dumpWithoutDefaultSettings(pw);
+ pw.println("processStateCache = " + mUidObserver.mProcStatesCache);
+ pw.decreaseIndent();
+ }
+
/** Write current settings into given {@link ProtoOutputStream}. */
- public void dumpProto(ProtoOutputStream proto) {
+ void dump(ProtoOutputStream proto) {
synchronized (mLock) {
proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 47b3e1a..f5d4d1e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -17,6 +17,7 @@
package com.android.server.vibrator;
import android.annotation.Nullable;
+import android.annotation.Nullable;
import android.hardware.vibrator.IVibrator;
import android.os.Binder;
import android.os.IVibratorStateListener;
@@ -26,6 +27,7 @@
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -351,6 +353,19 @@
+ '}';
}
+ void dump(IndentingPrintWriter pw) {
+ pw.println("VibratorController:");
+ pw.increaseIndent();
+ pw.println("isVibrating = " + mIsVibrating);
+ pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
+ pw.println("currentAmplitude = " + mCurrentAmplitude);
+ pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
+ pw.println("vibratorStateListenerCount = "
+ + mVibratorStateListeners.getRegisteredCallbackCount());
+ mVibratorInfo.dump(pw);
+ pw.decreaseIndent();
+ }
+
@GuardedBy("mLock")
private void notifyListenerOnVibrating(boolean isVibrating) {
if (mIsVibrating != isVibrating) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 6fdb1db..270f7f0c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -56,6 +56,7 @@
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,7 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -204,9 +206,15 @@
mNativeWrapper = injector.getNativeWrapper();
mNativeWrapper.init(listener);
- int dumpLimit = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+ int recentDumpSizeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_recentVibrationsDumpSizeLimit);
+ int dumpSizeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit);
+ int dumpAggregationTimeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer
+ .config_previousVibrationsDumpAggregationTimeMillisLimit);
+ mVibratorManagerRecords = new VibratorManagerRecords(
+ recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
mBatteryStatsService = injector.getBatteryStatsService();
mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
@@ -544,49 +552,74 @@
}
}
- private void dumpText(PrintWriter pw) {
+ private void dumpText(PrintWriter w) {
if (DEBUG) {
Slog.d(TAG, "Dumping vibrator manager service to text...");
}
+ IndentingPrintWriter pw = new IndentingPrintWriter(w, /* singleIndent= */ " ");
synchronized (mLock) {
pw.println("Vibrator Manager Service:");
- pw.println(" mVibrationSettings:");
- pw.println(" " + mVibrationSettings);
+ pw.increaseIndent();
+
+ mVibrationSettings.dump(pw);
pw.println();
- pw.println(" mVibratorControllers:");
+
+ pw.println("VibratorControllers:");
+ pw.increaseIndent();
for (int i = 0; i < mVibrators.size(); i++) {
- pw.println(" " + mVibrators.valueAt(i));
+ mVibrators.valueAt(i).dump(pw);
}
+ pw.decreaseIndent();
pw.println();
- pw.println(" mCurrentVibration:");
- pw.println(" " + (mCurrentVibration == null
- ? null : mCurrentVibration.getVibration().getDebugInfo()));
+
+ pw.println("CurrentVibration:");
+ pw.increaseIndent();
+ if (mCurrentVibration != null) {
+ mCurrentVibration.getVibration().getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
pw.println();
- pw.println(" mNextVibration:");
- pw.println(" " + (mNextVibration == null
- ? null : mNextVibration.getVibration().getDebugInfo()));
+
+ pw.println("NextVibration:");
+ pw.increaseIndent();
+ if (mNextVibration != null) {
+ mNextVibration.getVibration().getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
pw.println();
- pw.println(" mCurrentExternalVibration:");
- pw.println(" " + (mCurrentExternalVibration == null
- ? null : mCurrentExternalVibration.getDebugInfo()));
- pw.println();
+
+ pw.println("CurrentExternalVibration:");
+ pw.increaseIndent();
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
}
- mVibratorManagerRecords.dumpText(pw);
+
+ pw.println();
+ pw.println();
+ mVibratorManagerRecords.dump(pw);
}
- synchronized void dumpProto(FileDescriptor fd) {
+ private void dumpProto(FileDescriptor fd) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
if (DEBUG) {
Slog.d(TAG, "Dumping vibrator manager service to proto...");
}
synchronized (mLock) {
- mVibrationSettings.dumpProto(proto);
+ mVibrationSettings.dump(proto);
if (mCurrentVibration != null) {
- mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+ mCurrentVibration.getVibration().getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+ mCurrentExternalVibration.getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
@@ -601,7 +634,7 @@
proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
isUnderExternalControl);
}
- mVibratorManagerRecords.dumpProto(proto);
+ mVibratorManagerRecords.dump(proto);
proto.flush();
}
@@ -1581,64 +1614,105 @@
/** Keep records of vibrations played and provide debug information for this service. */
private static final class VibratorManagerRecords {
- private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
- new SparseArray<>();
- private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
- new LinkedList<>();
- private final int mPreviousVibrationsLimit;
+ private final VibrationRecords mAggregatedVibrationHistory;
+ private final VibrationRecords mRecentVibrations;
- VibratorManagerRecords(int limit) {
- mPreviousVibrationsLimit = limit;
+ VibratorManagerRecords(int recentVibrationSizeLimit, int aggregationSizeLimit,
+ int aggregationTimeLimit) {
+ mAggregatedVibrationHistory =
+ new VibrationRecords(aggregationSizeLimit, aggregationTimeLimit);
+ mRecentVibrations = new VibrationRecords(
+ recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
}
synchronized void record(HalVibration vib) {
- int usage = vib.callerInfo.attrs.getUsage();
- if (!mPreviousVibrations.contains(usage)) {
- mPreviousVibrations.put(usage, new LinkedList<>());
- }
- record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+ record(vib.getDebugInfo());
}
synchronized void record(ExternalVibrationHolder vib) {
- record(mPreviousExternalVibrations, vib.getDebugInfo());
+ record(vib.getDebugInfo());
}
- synchronized void record(LinkedList<Vibration.DebugInfo> records,
- Vibration.DebugInfo info) {
- if (records.size() > mPreviousVibrationsLimit) {
- records.removeFirst();
+ private synchronized void record(Vibration.DebugInfo info) {
+ AggregatedVibrationRecord removedRecord = mRecentVibrations.record(info);
+ if (removedRecord != null) {
+ mAggregatedVibrationHistory.record(removedRecord.mLatestVibration);
}
- records.addLast(info);
}
- synchronized void dumpText(PrintWriter pw) {
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
- pw.println();
- pw.print(" Previous vibrations for usage ");
- pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
- pw.println(":");
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- pw.println(" " + info);
+ synchronized void dump(IndentingPrintWriter pw) {
+ pw.println("Recent vibrations:");
+ pw.increaseIndent();
+ mRecentVibrations.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
+ pw.println();
+
+ pw.println("Aggregated vibration history:");
+ pw.increaseIndent();
+ mAggregatedVibrationHistory.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ synchronized void dump(ProtoOutputStream proto) {
+ mRecentVibrations.dump(proto);
+ }
+ }
+
+ /** Keep records of vibrations played and provide debug information for this service. */
+ private static final class VibrationRecords {
+ private final SparseArray<LinkedList<AggregatedVibrationRecord>> mVibrations =
+ new SparseArray<>();
+ private final int mSizeLimit;
+ private final int mAggregationTimeLimit;
+
+ VibrationRecords(int sizeLimit, int aggregationTimeLimit) {
+ mSizeLimit = sizeLimit;
+ mAggregationTimeLimit = aggregationTimeLimit;
+ }
+
+ synchronized AggregatedVibrationRecord record(Vibration.DebugInfo info) {
+ int usage = info.mCallerInfo.attrs.getUsage();
+ if (!mVibrations.contains(usage)) {
+ mVibrations.put(usage, new LinkedList<>());
+ }
+ LinkedList<AggregatedVibrationRecord> records = mVibrations.get(usage);
+ if (mAggregationTimeLimit > 0 && !records.isEmpty()) {
+ AggregatedVibrationRecord lastRecord = records.getLast();
+ if (lastRecord.mayAggregate(info, mAggregationTimeLimit)) {
+ lastRecord.record(info);
+ return null;
}
}
+ AggregatedVibrationRecord removedRecord = null;
+ if (records.size() > mSizeLimit) {
+ removedRecord = records.removeFirst();
+ }
+ records.addLast(new AggregatedVibrationRecord(info));
+ return removedRecord;
+ }
- pw.println();
- pw.println(" Previous external vibrations:");
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- pw.println(" " + info);
+ synchronized void dump(IndentingPrintWriter pw) {
+ for (int i = 0; i < mVibrations.size(); i++) {
+ pw.println(VibrationAttributes.usageToString(mVibrations.keyAt(i)) + ":");
+ pw.increaseIndent();
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ info.dump(pw);
+ }
+ pw.decreaseIndent();
+ pw.println();
}
}
- synchronized void dumpProto(ProtoOutputStream proto) {
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ synchronized void dump(ProtoOutputStream proto) {
+ for (int i = 0; i < mVibrations.size(); i++) {
long fieldId;
- switch (mPreviousVibrations.keyAt(i)) {
+ switch (mVibrations.keyAt(i)) {
case VibrationAttributes.USAGE_RINGTONE:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
break;
case VibrationAttributes.USAGE_NOTIFICATION:
- fieldId = VibratorManagerServiceDumpProto
- .PREVIOUS_NOTIFICATION_VIBRATIONS;
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS;
break;
case VibrationAttributes.USAGE_ALARM:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
@@ -1646,18 +1720,70 @@
default:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
}
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- info.dumpProto(proto, fieldId);
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ if (info.mLatestVibration.mPlayedEffect == null) {
+ // External vibrations are reported separately in the dump proto
+ info.dump(proto,
+ VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ } else {
+ info.dump(proto, fieldId);
+ }
}
}
+ }
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto,
- VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ synchronized void dumpOnSingleField(ProtoOutputStream proto, long fieldId) {
+ for (int i = 0; i < mVibrations.size(); i++) {
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ info.dump(proto, fieldId);
+ }
}
}
}
+ /**
+ * Record that keeps the last {@link Vibration.DebugInfo} played, aggregating close vibrations
+ * from the same uid that have the same {@link VibrationAttributes} and {@link VibrationEffect}.
+ */
+ private static final class AggregatedVibrationRecord {
+ private final Vibration.DebugInfo mFirstVibration;
+ private Vibration.DebugInfo mLatestVibration;
+ private int mVibrationCount;
+
+ AggregatedVibrationRecord(Vibration.DebugInfo info) {
+ mLatestVibration = mFirstVibration = info;
+ mVibrationCount = 1;
+ }
+
+ synchronized boolean mayAggregate(Vibration.DebugInfo info, long timeLimit) {
+ return Objects.equals(mLatestVibration.mCallerInfo.uid, info.mCallerInfo.uid)
+ && Objects.equals(mLatestVibration.mCallerInfo.attrs, info.mCallerInfo.attrs)
+ && Objects.equals(mLatestVibration.mPlayedEffect, info.mPlayedEffect)
+ && Math.abs(mLatestVibration.mCreateTime - info.mCreateTime) < timeLimit;
+ }
+
+ synchronized void record(Vibration.DebugInfo vib) {
+ mLatestVibration = vib;
+ mVibrationCount++;
+ }
+
+ synchronized void dump(IndentingPrintWriter pw) {
+ mFirstVibration.dumpCompact(pw);
+ if (mVibrationCount == 1) {
+ return;
+ }
+ if (mVibrationCount > 2) {
+ pw.println(
+ "-> Skipping " + (mVibrationCount - 2) + " aggregated vibrations, latest:");
+ }
+ mLatestVibration.dumpCompact(pw);
+ }
+
+ synchronized void dump(ProtoOutputStream proto, long fieldId) {
+ mLatestVibration.dump(proto, fieldId);
+ }
+ }
+
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
@@ -1676,7 +1802,7 @@
/**
* Ends the external vibration, and clears related service state.
*
- * @param vibrationEndInfo the status and related info to end the associated Vibration with
+ * @param vibrationEndInfo the status and related info to end the associated Vibration
* @param continueExternalControl indicates whether external control will continue. If not, the
* HAL will have external control turned off.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ea06b42..a7849c1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,6 +226,7 @@
import static com.android.server.wm.LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE;
@@ -2682,7 +2683,9 @@
private boolean transferSplashScreenIfNeeded() {
if (finishing || !mHandleExitSplashScreen || mStartingSurface == null
|| mStartingWindow == null
- || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH) {
+ || mTransferringSplashScreenState == TRANSFER_SPLASH_SCREEN_FINISH
+ // skip copy splash screen to client if it was resized
+ || (mStartingData != null && mStartingData.mResizedFromTransfer)) {
return false;
}
if (isTransferringSplashScreen()) {
@@ -7644,7 +7647,8 @@
@Override
void prepareSurfaces() {
final boolean show = isVisible() || isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS
+ | ANIMATION_TYPE_PREDICT_BACK);
if (mSurfaceControl != null) {
if (show && !mLastSurfaceShowing) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 0c196d7..976641b 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,7 +26,9 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
+import static com.android.server.wm.BackNavigationProto.ANIMATION_RUNNING;
import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE;
+import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY;
import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
@@ -50,6 +52,7 @@
import android.window.BackAnimationAdapter;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
+import android.window.IWindowlessStartingSurfaceCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.TaskSnapshot;
@@ -73,6 +76,8 @@
private @BackNavigationInfo.BackTargetType int mLastBackType;
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
+
+ private boolean mBackAnimationRunning;
private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
private AnimationHandler mAnimationHandler;
@@ -474,7 +479,7 @@
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
- mAnimationHandler.mOpenTransitionTargetMatch = true;
+ mAnimationHandler.markStartingSurfaceMatch();
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -583,8 +588,9 @@
* The closing target should only exist in close list, but the opening target can be either in
* open or close list.
*/
- void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
- if (!isMonitoringTransition()) {
+ void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets,
+ SurfaceControl.Transaction startTransaction) {
+ if (!isMonitoringTransition() || targets.isEmpty()) {
return;
}
for (int i = targets.size() - 1; i >= 0; --i) {
@@ -613,6 +619,17 @@
Slog.e(TAG, "Gesture animation is applied on another transition?");
}
mWaitTransitionFinish = transition;
+ // Flag target matches to defer remove the splash screen.
+ for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = mTmpOpenApps.get(i);
+ if (mAnimationHandler.isTarget(wc, true /* open */)) {
+ mAnimationHandler.markStartingSurfaceMatch();
+ break;
+ }
+ }
+ // Because the target will reparent to transition root, so it cannot be controlled by
+ // animation leash. Hide the close target when transition starts.
+ startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
}
mTmpOpenApps.clear();
mTmpCloseApps.clear();
@@ -633,6 +650,7 @@
mAnimationHandler.clearBackAnimateTarget();
mNavigationMonitor.stopMonitorTransition();
mWaitTransitionFinish = null;
+ mBackAnimationRunning = false;
}
/**
@@ -717,11 +735,7 @@
// This will be set before transition happen, to know whether the real opening target
// exactly match animating target. When target match, reparent the starting surface to
// the opening target like starting window do.
- private boolean mOpenTransitionTargetMatch;
- // The starting surface task Id. Used to clear the starting surface if the animation has
- // request one during animating.
- private int mRequestedStartingSurfaceTaskId;
- private SurfaceControl mStartingSurface;
+ private boolean mStartingSurfaceTargetMatch;
private ActivityRecord mOpenActivity;
AnimationHandler(WindowManagerService wms) {
@@ -765,8 +779,8 @@
return;
}
- mCloseAdaptor = createAdaptor(closeTarget, false /* isOpen */);
- mOpenAdaptor = createAdaptor(open, true /* isOpen */);
+ mCloseAdaptor = createAdaptor(closeTarget, false, mSwitchType);
+ mOpenAdaptor = createAdaptor(open, true, mSwitchType);
mOpenActivity = openActivity;
if (mCloseAdaptor.mAnimationTarget == null || mOpenAdaptor.mAnimationTarget == null) {
Slog.w(TAG, "composeNewAnimations fail, skip");
@@ -774,8 +788,8 @@
}
}
- boolean composeAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open,
- ActivityRecord openActivity) {
+ private boolean composeAnimations(@NonNull WindowContainer close,
+ @NonNull WindowContainer open, ActivityRecord openActivity) {
if (mComposed || mWaitTransition) {
Slog.e(TAG, "Previous animation is running " + this);
return false;
@@ -805,28 +819,6 @@
.isSupportWindowlessStartingSurface();
}
- void createStartingSurface(TaskSnapshot snapshot) {
- if (!mComposed) {
- return;
- }
-
- final ActivityRecord topActivity = getTopOpenActivity();
- if (topActivity == null) {
- Slog.e(TAG, "createStartingSurface fail, no open activity: " + this);
- return;
- }
- // TODO (b/257857570) draw snapshot by starting surface.
- }
-
- private ActivityRecord getTopOpenActivity() {
- if (mSwitchType == ACTIVITY_SWITCH) {
- return mOpenAdaptor.mTarget.asActivityRecord();
- } else if (mSwitchType == TASK_SWITCH) {
- return mOpenAdaptor.mTarget.asTask().getTopNonFinishingActivity();
- }
- return null;
- }
-
boolean containTarget(ArrayList<WindowContainer> wcs, boolean open) {
for (int i = wcs.size() - 1; i >= 0; --i) {
if (isTarget(wcs.get(i), open)) {
@@ -860,13 +852,13 @@
if (!mComposed) {
return;
}
- cleanUpWindowlessSurface();
if (mCloseAdaptor != null) {
mCloseAdaptor.mTarget.cancelAnimation();
mCloseAdaptor = null;
}
if (mOpenAdaptor != null) {
+ mOpenAdaptor.cleanUpWindowlessSurface(mStartingSurfaceTargetMatch);
mOpenAdaptor.mTarget.cancelAnimation();
mOpenAdaptor = null;
}
@@ -875,36 +867,16 @@
}
}
- private void cleanUpWindowlessSurface() {
- final ActivityRecord ar = getTopOpenActivity();
- if (ar == null) {
- Slog.w(TAG, "finishPresentAnimations without top activity: " + this);
- }
- final SurfaceControl.Transaction pendingT = ar != null ? ar.getPendingTransaction()
- : mOpenAdaptor.mTarget.getPendingTransaction();
- // ensure open target is visible before cancel animation.
- mOpenTransitionTargetMatch &= ar != null;
- if (mOpenTransitionTargetMatch) {
- pendingT.show(ar.getSurfaceControl());
- }
- if (mRequestedStartingSurfaceTaskId != 0) {
- // If open target match, reparent to open activity
- if (mStartingSurface != null && mOpenTransitionTargetMatch) {
- pendingT.reparent(mStartingSurface, ar.getSurfaceControl());
- }
- // remove starting surface.
- mStartingSurface = null;
- // TODO (b/257857570) draw snapshot by starting surface.
- mRequestedStartingSurfaceTaskId = 0;
- }
+ void markStartingSurfaceMatch() {
+ mStartingSurfaceTargetMatch = true;
+ mOpenAdaptor.reparentWindowlessSurfaceToTarget();
}
void clearBackAnimateTarget() {
finishPresentAnimations();
mComposed = false;
mWaitTransition = false;
- mOpenTransitionTargetMatch = false;
- mRequestedStartingSurfaceTaskId = 0;
+ mStartingSurfaceTargetMatch = false;
mSwitchType = UNKNOWN;
mOpenActivity = null;
}
@@ -935,9 +907,9 @@
}
private static BackWindowAnimationAdaptor createAdaptor(
- WindowContainer target, boolean isOpen) {
+ WindowContainer target, boolean isOpen, int switchType) {
final BackWindowAnimationAdaptor adaptor =
- new BackWindowAnimationAdaptor(target, isOpen);
+ new BackWindowAnimationAdaptor(target, isOpen, switchType);
final SurfaceControl.Transaction pt = target.getPendingTransaction();
target.startAnimation(pt, adaptor, false /* hidden */, ANIMATION_TYPE_PREDICT_BACK);
// Workaround to show TaskFragment which can be hide in Transitions and won't show
@@ -957,11 +929,19 @@
private final WindowContainer mTarget;
private final boolean mIsOpen;
private RemoteAnimationTarget mAnimationTarget;
+ private final int mSwitchType;
- BackWindowAnimationAdaptor(WindowContainer closeTarget, boolean isOpen) {
- mBounds.set(closeTarget.getBounds());
- mTarget = closeTarget;
+ // The starting surface task Id. Used to clear the starting surface if the animation has
+ // requested one during animating.
+ private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ private SurfaceControl mStartingSurface;
+
+ BackWindowAnimationAdaptor(WindowContainer target, boolean isOpen,
+ int switchType) {
+ mBounds.set(target.getBounds());
+ mTarget = target;
mIsOpen = isOpen;
+ mSwitchType = switchType;
}
@Override
public boolean getShowWallpaper() {
@@ -979,6 +959,8 @@
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ mStartingSurface = null;
}
}
@@ -1009,8 +991,15 @@
return mAnimationTarget;
}
Task t = mTarget.asTask();
- final ActivityRecord r = t != null ? t.getTopNonFinishingActivity()
- : mTarget.asActivityRecord();
+ ActivityRecord r = null;
+ if (t == null && mTarget.asTaskFragment() != null) {
+ t = mTarget.asTaskFragment().getTask();
+ r = mTarget.asTaskFragment().getTopNonFinishingActivity();
+ }
+ if (r == null) {
+ r = t != null ? t.getTopNonFinishingActivity()
+ : mTarget.asActivityRecord();
+ }
if (t == null && r != null) {
t = r.getTask();
}
@@ -1037,6 +1026,77 @@
r.checkEnterPictureInPictureAppOpsState());
return mAnimationTarget;
}
+
+ void createStartingSurface() {
+ if (!mIsOpen) {
+ return;
+ }
+ final Task openTask = mSwitchType == TASK_SWITCH
+ ? mTarget.asTask() : mSwitchType == ACTIVITY_SWITCH
+ ? mTarget.asActivityRecord().getTask() : null;
+ if (openTask == null) {
+ return;
+ }
+ final ActivityRecord mainActivity = mSwitchType == ACTIVITY_SWITCH
+ ? mTarget.asActivityRecord()
+ : openTask.getTopNonFinishingActivity();
+ if (mainActivity == null) {
+ return;
+ }
+ final TaskSnapshot snapshot = getSnapshot(mTarget);
+ mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
+ .addWindowlessStartingSurface(openTask, mainActivity,
+ mAnimationTarget.leash, snapshot,
+ new IWindowlessStartingSurfaceCallback.Stub() {
+ // Once the starting surface has been created in shell, it will call
+ // onSurfaceAdded to pass the created surface to core, so if a
+ // transition is triggered by the back gesture, there doesn't need to
+ // create another starting surface for the opening target, just reparent
+ // the starting surface to the opening target.
+ // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
+ // called, there won't be able to reparent the starting surface on
+ // opening target. But if that happens and transition target is matched,
+ // the app window should already draw.
+ @Override
+ public void onSurfaceAdded(SurfaceControl sc) {
+ synchronized (mTarget.mWmService.mGlobalLock) {
+ if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
+ mStartingSurface = sc;
+ }
+ }
+ }
+ });
+ }
+
+ // When back gesture has triggered and transition target matches navigation target,
+ // reparent the starting surface to the opening target as it's starting window.
+ void reparentWindowlessSurfaceToTarget() {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ // If open target matches, reparent to open activity or task
+ if (mStartingSurface != null && mStartingSurface.isValid()) {
+ mTarget.getPendingTransaction()
+ .reparent(mStartingSurface, mTarget.getSurfaceControl());
+ // remove starting surface.
+ mStartingSurface = null;
+ }
+ }
+
+ /**
+ * Ask shell to clear the starting surface.
+ * @param openTransitionMatch if true, shell will play the remove starting window
+ * animation, otherwise remove it directly.
+ */
+ void cleanUpWindowlessSurface(boolean openTransitionMatch) {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ mTarget.mWmService.mAtmService.mTaskOrganizerController
+ .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
+ !openTransitionMatch);
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ }
}
ScheduleAnimationBuilder prepareAnimation(int backType, BackAnimationAdapter adapter,
@@ -1089,15 +1149,13 @@
/**
* Apply preview strategy on the opening target
- * @param open The opening target.
+ * @param openAnimationAdaptor The animator who can create starting surface.
* @param visibleOpenActivity The visible activity in opening target.
- * @return If the preview strategy is launch behind, returns the Activity that has
- * launchBehind set, or null otherwise.
*/
- private void applyPreviewStrategy(WindowContainer open,
+ private void applyPreviewStrategy(BackWindowAnimationAdaptor openAnimationAdaptor,
ActivityRecord visibleOpenActivity) {
if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
- createStartingSurface(getSnapshot(open));
+ openAnimationAdaptor.createStartingSurface();
return;
}
setLaunchBehind(visibleOpenActivity);
@@ -1119,7 +1177,7 @@
if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
return null;
}
- applyPreviewStrategy(mOpenTarget, openActivity);
+ applyPreviewStrategy(mOpenAdaptor, openActivity);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1220,6 +1278,7 @@
if (mPendingAnimation != null) {
mPendingAnimation.run();
mPendingAnimation = null;
+ mBackAnimationRunning = true;
}
}
@@ -1236,9 +1295,6 @@
}
static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
- if (!isScreenshotEnabled()) {
- return null;
- }
if (w.asTask() != null) {
final Task task = w.asTask();
return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
@@ -1247,8 +1303,8 @@
}
if (w.asActivityRecord() != null) {
- // TODO (b/259497289) return TaskSnapshot when feature complete.
- return null;
+ final ActivityRecord ar = w.asActivityRecord();
+ return ar.mWmService.mSnapshotController.mActivitySnapshotController.getSnapshot(ar);
}
return null;
}
@@ -1270,6 +1326,12 @@
proto.write(ANIMATION_IN_PROGRESS, mBackAnimationInProgress);
proto.write(LAST_BACK_TYPE, mLastBackType);
proto.write(SHOW_WALLPAPER, mShowWallpaper);
+ if (mAnimationHandler.mOpenActivity != null) {
+ mAnimationHandler.mOpenActivity.writeNameToProto(proto, MAIN_OPEN_ACTIVITY);
+ } else {
+ proto.write(MAIN_OPEN_ACTIVITY, "");
+ }
+ proto.write(ANIMATION_RUNNING, mBackAnimationRunning);
proto.end(token);
}
}
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index cff86ad..2b22d75 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,10 @@
*/
Task mAssociatedTask;
+
+ /** Whether the starting window is resized from transfer across activities. */
+ boolean mResizedFromTransfer;
+
/** Whether the starting window is drawn. */
boolean mIsDisplayed;
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index cdb4ad6..b72d027 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -790,12 +790,8 @@
}
boolean isSupportWindowlessStartingSurface() {
- // Enable after ag/20426257
final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
- if (lastOrganizer == null) {
- return false;
- }
- return false;
+ return lastOrganizer != null;
}
/**
* Notify the shell ({@link com.android.wm.shell.ShellTaskOrganizer} that the client has
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 789e3d2..7a904f8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1499,7 +1499,8 @@
mTargets = calculateTargets(mParticipants, mChanges);
// Check whether the participants were animated from back navigation.
- mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
+ mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets,
+ transaction);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
mController.assignTrack(this, info);
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 26aab07..726ae5c 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -76,7 +76,7 @@
@NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
@WindowType int type, @Nullable Bundle options) {
registerWindowContainerListener(wpc, clientToken, container, type, options,
- true /* shouDispatchConfigWhenRegistering */);
+ true /* shouldDispatchConfigWhenRegistering */);
}
/**
@@ -91,19 +91,19 @@
* @param container the {@link WindowContainer} which the listener is going to listen to.
* @param type the window type
* @param options a bundle used to pass window-related options.
- * @param shouDispatchConfigWhenRegistering {@code true} to indicate the current
+ * @param shouldDispatchConfigWhenRegistering {@code true} to indicate the current
* {@code container}'s config will dispatch to the client side when
* registering the {@link WindowContextListenerImpl}
*/
void registerWindowContainerListener(@NonNull WindowProcessController wpc,
@NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
@WindowType int type, @Nullable Bundle options,
- boolean shouDispatchConfigWhenRegistering) {
+ boolean shouldDispatchConfigWhenRegistering) {
WindowContextListenerImpl listener = mListeners.get(clientToken);
if (listener == null) {
listener = new WindowContextListenerImpl(wpc, clientToken, container, type,
options);
- listener.register(shouDispatchConfigWhenRegistering);
+ listener.register(shouldDispatchConfigWhenRegistering);
} else {
updateContainerForWindowContextListener(clientToken, container);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 210378f..261d6bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -305,6 +305,7 @@
import android.window.ScreenCapture;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
+import android.window.WindowContextInfo;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -2737,7 +2738,7 @@
@Nullable
@Override
- public Configuration attachWindowContextToDisplayArea(@NonNull IApplicationThread appThread,
+ public WindowContextInfo attachWindowContextToDisplayArea(@NonNull IApplicationThread appThread,
@NonNull IBinder clientToken, @LayoutParams.WindowType int type, int displayId,
@Nullable Bundle options) {
Objects.requireNonNull(appThread);
@@ -2766,8 +2767,8 @@
final DisplayArea<?> da = dc.findAreaForWindowType(type, options,
callerCanManageAppTokens, false /* roundedCornerOverlay */);
mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
- da, type, options, false /* shouDispatchConfigWhenRegistering */);
- return da.getConfiguration();
+ da, type, options, false /* shouldDispatchConfigWhenRegistering */);
+ return new WindowContextInfo(da.getConfiguration(), displayId);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -2776,8 +2777,8 @@
@Nullable
@Override
- public Configuration attachWindowContextToDisplayContent(@NonNull IApplicationThread appThread,
- @NonNull IBinder clientToken, int displayId) {
+ public WindowContextInfo attachWindowContextToDisplayContent(
+ @NonNull IApplicationThread appThread, @NonNull IBinder clientToken, int displayId) {
Objects.requireNonNull(appThread);
Objects.requireNonNull(clientToken);
final int callingPid = Binder.getCallingPid();
@@ -2809,16 +2810,17 @@
mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
dc, INVALID_WINDOW_TYPE, null /* options */,
- false /* shouDispatchConfigWhenRegistering */);
- return dc.getConfiguration();
+ false /* shouldDispatchConfigWhenRegistering */);
+ return new WindowContextInfo(dc.getConfiguration(), displayId);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
+ @Nullable
@Override
- public void attachWindowContextToWindowToken(@NonNull IApplicationThread appThread,
+ public WindowContextInfo attachWindowContextToWindowToken(@NonNull IApplicationThread appThread,
@NonNull IBinder clientToken, @NonNull IBinder token) {
Objects.requireNonNull(appThread);
Objects.requireNonNull(clientToken);
@@ -2834,13 +2836,13 @@
if (wpc == null) {
ProtoLog.w(WM_ERROR, "attachWindowContextToWindowToken: calling from"
+ " non-existing process pid=%d uid=%d", callingPid, callingUid);
- return;
+ return null;
}
final WindowToken windowToken = mRoot.getWindowToken(token);
if (windowToken == null) {
ProtoLog.w(WM_ERROR, "Then token:%s is invalid. It might be "
+ "removed", token);
- return;
+ return null;
}
final int type = mWindowContextListenerController.getWindowType(clientToken);
if (type == INVALID_WINDOW_TYPE) {
@@ -2854,10 +2856,13 @@
}
if (!mWindowContextListenerController.assertCallerCanModifyListener(clientToken,
callerCanManageAppTokens, callingUid)) {
- return;
+ return null;
}
mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
- windowToken, windowToken.windowType, windowToken.mOptions);
+ windowToken, windowToken.windowType, windowToken.mOptions,
+ false /* shouldDispatchConfigWhenRegistering */);
+ return new WindowContextInfo(windowToken.getConfiguration(),
+ windowToken.getDisplayContent().getDisplayId());
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index baf97b7..029f46f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1935,7 +1935,9 @@
}
final ActivityRecord atoken = mActivityRecord;
if (atoken != null) {
- return ((!isParentWindowHidden() && atoken.isVisible())
+ final boolean isVisible = isStartingWindowAssociatedToTask()
+ ? mStartingData.mAssociatedTask.isVisible() : atoken.isVisible();
+ return ((!isParentWindowHidden() && isVisible)
|| isAnimationRunningSelfOrParent());
}
final WallpaperWindowToken wtoken = mToken.asWallpaperToken();
@@ -2330,6 +2332,13 @@
// IME surface association. (e.g. Attach IME surface on the display instead of the
// app when the app bounds being letterboxed.)
mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
+ // Fix the starting window to task when Activity has changed.
+ if (mStartingData != null && mStartingData.mAssociatedTask == null
+ && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
+ mStartingData.mResizedFromTransfer = true;
+ // Lock the starting window to task, so it won't resize from transfer anymore.
+ mActivityRecord.associateStartingWindowWithTaskIfNeeded();
+ }
}
}
@@ -3907,7 +3916,7 @@
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
- return mActivityRecord != null
+ return mActivityRecord != null && !isStartingWindowAssociatedToTask()
&& (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
}
@@ -5673,6 +5682,12 @@
// TODO(b/233286785): Add sync support to wallpaper.
return true;
}
+ if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+ && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+ && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+ // Skip sync for invisible app windows which are not managed by activity lifecycle.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING
new file mode 100644
index 0000000..ea44d06
--- /dev/null
+++ b/services/core/jni/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+ "presubmit": [
+ {
+ "file_patterns": [
+ "[^/]*(vibrator)[^/]*\\.[^/]*"
+ ],
+ "name": "CtsVibratorTestCases",
+ "options": [
+ {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ]
+}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index c0cfa53..486ddb4 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -420,14 +420,11 @@
setDeviceServer(server);
}
- @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
- Manifest.permission.CREATE_USERS,
- Manifest.permission.MANAGE_USERS})
public Device(BluetoothDevice bluetoothDevice) {
mBluetoothDevice = bluetoothDevice;
mServiceInfo = null;
mUid = mBluetoothServiceUid;
- mUserId = mUserManager.getMainUser().getIdentifier();
+ mUserId = UserHandle.getUserId(mUid);
}
private void setDeviceServer(IMidiDeviceServer server) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 17474fb..6a349e2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -26,6 +26,7 @@
import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
import com.android.server.permission.access.immutable.IndexedMap
import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
import com.android.server.permission.access.util.attributeInt
import com.android.server.permission.access.util.attributeInterned
import com.android.server.permission.access.util.forEachTag
@@ -46,6 +47,7 @@
getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
}
addPolicy(AppIdPermissionPolicy())
+ addPolicy(DevicePermissionPolicy())
addPolicy(AppIdAppOpPolicy())
addPolicy(PackageAppOpPolicy())
} as IndexedMap<String, IndexedMap<String, SchemePolicy>>
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 4ec32ea..94c878a 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -329,6 +329,18 @@
private typealias AppIdPermissionFlagsReference =
MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
+
+typealias DevicePermissionFlags =
+ IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableDevicePermissionFlags =
+ MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias AppIdDevicePermissionFlags =
+ IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+typealias MutableAppIdDevicePermissionFlags =
+ MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+private typealias AppIdDevicePermissionFlagsReference =
+ MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags>
+
typealias AppIdAppOpModes =
IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
typealias MutableAppIdAppOpModes =
@@ -346,6 +358,7 @@
sealed class UserState(
internal val packageVersionsReference: PackageVersionsReference,
internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+ internal val appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
internal val appIdAppOpModesReference: AppIdAppOpModesReference,
internal val packageAppOpModesReference: PackageAppOpModesReference,
defaultPermissionGrantFingerprint: String?,
@@ -357,6 +370,9 @@
val appIdPermissionFlags: AppIdPermissionFlags
get() = appIdPermissionFlagsReference.get()
+ val appIdDevicePermissionFlags: AppIdDevicePermissionFlags
+ get() = appIdDevicePermissionFlagsReference.get()
+
val appIdAppOpModes: AppIdAppOpModes
get() = appIdAppOpModesReference.get()
@@ -375,6 +391,7 @@
class MutableUserState private constructor(
packageVersionsReference: PackageVersionsReference,
appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+ appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
appIdAppOpModesReference: AppIdAppOpModesReference,
packageAppOpModesReference: PackageAppOpModesReference,
defaultPermissionGrantFingerprint: String?,
@@ -382,6 +399,7 @@
) : UserState(
packageVersionsReference,
appIdPermissionFlagsReference,
+ appIdDevicePermissionFlagsReference,
appIdAppOpModesReference,
packageAppOpModesReference,
defaultPermissionGrantFingerprint,
@@ -390,6 +408,7 @@
constructor() : this(
PackageVersionsReference(MutableIndexedMap<String, Int>()),
AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+ AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
AppIdAppOpModesReference(MutableAppIdAppOpModes()),
PackageAppOpModesReference(MutablePackageAppOpModes()),
null,
@@ -399,6 +418,7 @@
internal constructor(userState: UserState) : this(
userState.packageVersionsReference.toImmutable(),
userState.appIdPermissionFlagsReference.toImmutable(),
+ userState.appIdDevicePermissionFlagsReference.toImmutable(),
userState.appIdAppOpModesReference.toImmutable(),
userState.packageAppOpModesReference.toImmutable(),
userState.defaultPermissionGrantFingerprint,
@@ -410,6 +430,9 @@
fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags =
appIdPermissionFlagsReference.mutate()
+ fun mutateAppIdDevicePermissionFlags(): MutableAppIdDevicePermissionFlags =
+ appIdDevicePermissionFlagsReference.mutate()
+
fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate()
fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index d1abc04..1d46ca7 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -65,6 +65,17 @@
}
}
+data class DevicePermissionUri(
+ val permissionName: String,
+ val deviceId: Int
+) : AccessUri(SCHEME) {
+ override fun toString(): String = "$scheme:///$permissionName/$deviceId"
+
+ companion object {
+ const val SCHEME = "device-permission"
+ }
+}
+
data class UidUri(
val uid: Int
) : AccessUri(SCHEME) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
index 6108ad2..ce4aa44 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -16,6 +16,9 @@
package com.android.server.permission.access.immutable
+/**
+ * Immutable list with index-based access.
+ */
sealed class IndexedList<T>(
internal val list: ArrayList<T>
) : Immutable<MutableIndexedList<T>> {
@@ -34,6 +37,9 @@
override fun toString(): String = list.toString()
}
+/**
+ * Mutable list with index-based access.
+ */
class MutableIndexedList<T>(
list: ArrayList<T> = ArrayList()
) : IndexedList<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
index 1202c81..77e71ba 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -16,6 +16,9 @@
package com.android.server.permission.access.immutable
+/**
+ * Immutable set with index-based access, implemented using a list.
+ */
sealed class IndexedListSet<T>(
internal val list: ArrayList<T>
) : Immutable<MutableIndexedListSet<T>> {
@@ -36,6 +39,9 @@
override fun toString(): String = list.toString()
}
+/**
+ * Mutable set with index-based access, implemented using a list.
+ */
class MutableIndexedListSet<T>(
list: ArrayList<T> = ArrayList()
) : IndexedListSet<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
index 5c75de8..299cc89 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -18,6 +18,9 @@
import android.util.ArrayMap
+/**
+ * Immutable map with index-based access.
+ */
sealed class IndexedMap<K, V>(
internal val map: ArrayMap<K, V>
) : Immutable<MutableIndexedMap<K, V>> {
@@ -42,6 +45,9 @@
override fun toString(): String = map.toString()
}
+/**
+ * Mutable map with index-based access.
+ */
class MutableIndexedMap<K, V>(
map: ArrayMap<K, V> = ArrayMap()
) : IndexedMap<K, V>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
index 8c963aa..ff76a47 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -18,6 +18,11 @@
import android.util.ArrayMap
+/**
+ * Immutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>(
internal val map: ArrayMap<K, MutableReference<I, M>>
) : Immutable<MutableIndexedReferenceMap<K, I, M>> {
@@ -42,6 +47,11 @@
override fun toString(): String = map.toString()
}
+/**
+ * Mutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
) : IndexedReferenceMap<K, I, M>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
index 9868616..547e56c 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -18,6 +18,9 @@
import android.util.ArraySet
+/**
+ * Immutable set with index-based access.
+ */
sealed class IndexedSet<T>(
internal val set: ArraySet<T>
) : Immutable<MutableIndexedSet<T>> {
@@ -37,6 +40,9 @@
override fun toString(): String = set.toString()
}
+/**
+ * Mutable set with index-based access.
+ */
class MutableIndexedSet<T>(
set: ArraySet<T> = ArraySet()
) : IndexedSet<T>(set) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
index b7d8b4c..7ed29e8 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -18,6 +18,9 @@
import android.util.SparseArray
+/**
+ * Immutable map with index-based access and [Int] keys.
+ */
sealed class IntMap<T>(
internal val array: SparseArray<T>
) : Immutable<MutableIntMap<T>> {
@@ -41,6 +44,9 @@
override fun toString(): String = array.toString()
}
+/**
+ * Mutable map with index-based access and [Int] keys.
+ */
class MutableIntMap<T>(
array: SparseArray<T> = SparseArray()
) : IntMap<T>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
index 22fa8f2..160b227 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -18,6 +18,11 @@
import android.util.SparseArray
+/**
+ * Immutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
sealed class IntReferenceMap<I : Immutable<M>, M : I>(
internal val array: SparseArray<MutableReference<I, M>>
) : Immutable<MutableIntReferenceMap<I, M>> {
@@ -42,6 +47,11 @@
override fun toString(): String = array.toString()
}
+/**
+ * Mutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
class MutableIntReferenceMap<I : Immutable<M>, M : I>(
array: SparseArray<MutableReference<I, M>> = SparseArray()
) : IntReferenceMap<I, M>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
index 9da3671..21f2af2 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -18,6 +18,9 @@
import android.util.SparseBooleanArray
+/**
+ * Immutable set with index-based access and [Int] elements.
+ */
sealed class IntSet(
internal val array: SparseBooleanArray
) : Immutable<MutableIntSet> {
@@ -37,6 +40,9 @@
override fun toString(): String = array.toString()
}
+/**
+ * Mutable set with index-based access and [Int] elements.
+ */
class MutableIntSet(
array: SparseBooleanArray = SparseBooleanArray()
) : IntSet(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
index e39a3bb..171cfeb 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -16,14 +16,39 @@
package com.android.server.permission.access.immutable
+/**
+ * Wrapper class for reference to a mutable data structure instance.
+ *
+ * This class encapsulates the logic to mutate/copy a mutable data structure instance and update the
+ * reference to the new mutated instance. It also remembers the mutated instance so that it can be
+ * reused during further mutations.
+ *
+ * Instances of this class should be kept private within a data structure, with the [get] method
+ * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate]
+ * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the
+ * data structure is mutated/copied, a new instance of this class should be obtained with
+ * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents
+ * further modifications to a data structure accessed with its immutable interface.
+ *
+ * @see MutableIndexedReferenceMap
+ * @see MutableIntReferenceMap
+ */
class MutableReference<I : Immutable<M>, M : I> private constructor(
private var immutable: I,
private var mutable: M?
) {
constructor(mutable: M) : this(mutable, mutable)
+ /**
+ * Return an immutable reference to the wrapped mutable data structure.
+ */
fun get(): I = immutable
+ /**
+ * Make the wrapped mutable data structure mutable, by either calling [Immutable.toMutable] and
+ * replacing the wrapped reference with its result, or reusing the existing reference if it's
+ * already mutable.
+ */
fun mutate(): M {
mutable?.let { return it }
return immutable.toMutable().also {
@@ -32,6 +57,10 @@
}
}
+ /**
+ * Create a new [MutableReference] instance with the wrapped mutable data structure being
+ * immutable-only again.
+ */
fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
override fun equals(other: Any?): Boolean {
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
new file mode 100644
index 0000000..37a4a90
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.permission.access.permission
+
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.DevicePermissionFlags
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdDevicePermissionFlags
+import com.android.server.permission.access.MutableDevicePermissionFlags
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.immutable.IndexedMap
+import com.android.server.permission.access.immutable.MutableIndexedMap
+import com.android.server.permission.access.immutable.forEachIndexed
+import com.android.server.permission.access.immutable.forEachReversedIndexed
+import com.android.server.permission.access.immutable.set
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class DevicePermissionPersistence {
+ fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+ when (tagName) {
+ TAG_APP_ID_DEVICE_PERMISSIONS -> parseAppIdDevicePermissions(state, userId)
+ else -> {}
+ }
+ }
+
+ private fun BinaryXmlPullParser.parseAppIdDevicePermissions(
+ state: MutableAccessState,
+ userId: Int
+ ) {
+ val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+ val appIdDevicePermissionFlags = userState.mutateAppIdDevicePermissionFlags()
+ forEachTag {
+ when (tagName) {
+ TAG_APP_ID -> parseAppId(appIdDevicePermissionFlags)
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+ }
+ }
+
+ appIdDevicePermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
+ if (appId !in state.externalState.appIdPackageNames) {
+ Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
+ appIdDevicePermissionFlags.removeAt(appIdIndex)
+ userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+ }
+ }
+ }
+
+ private fun BinaryXmlPullParser.parseAppId(
+ appIdPermissionFlags: MutableAppIdDevicePermissionFlags
+ ) {
+ val appId = getAttributeIntOrThrow(ATTR_ID)
+ val devicePermissionFlags = MutableDevicePermissionFlags()
+ appIdPermissionFlags[appId] = devicePermissionFlags
+ forEachTag {
+ when (tagName) {
+ TAG_DEVICE -> parseDevice(devicePermissionFlags)
+ else -> {
+ Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+ }
+ }
+ }
+ }
+
+ private fun BinaryXmlPullParser.parseDevice(
+ deviceIdPermissionFlags: MutableDevicePermissionFlags
+ ) {
+ val deviceId = getAttributeValueOrThrow(ATTR_ID)
+ val permissionFlags = MutableIndexedMap<String, Int>()
+ deviceIdPermissionFlags.put(deviceId, permissionFlags)
+ forEachTag {
+ when (tagName) {
+ TAG_PERMISSION -> parsePermission(permissionFlags)
+ else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+ }
+ }
+ }
+
+ private fun BinaryXmlPullParser.parsePermission(
+ permissionFlags: MutableIndexedMap<String, Int>
+ ) {
+ val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+ val flags = getAttributeIntOrThrow(ATTR_FLAGS)
+ permissionFlags[name] = flags
+ }
+
+ fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+ val appIdDevicePermissionFlags = state.userStates[userId]!!.appIdDevicePermissionFlags
+ tag(TAG_APP_ID_DEVICE_PERMISSIONS) {
+ appIdDevicePermissionFlags.forEachIndexed { _, appId, devicePermissionFlags ->
+ serializeAppId(appId, devicePermissionFlags)
+ }
+ }
+ }
+
+ private fun BinaryXmlSerializer.serializeAppId(
+ appId: Int,
+ devicePermissionFlags: DevicePermissionFlags
+ ) {
+ tag(TAG_APP_ID) {
+ attributeInt(ATTR_ID, appId)
+ devicePermissionFlags.forEachIndexed { _, deviceId, permissionFlags ->
+ serializeDevice(deviceId, permissionFlags)
+ }
+ }
+ }
+
+ private fun BinaryXmlSerializer.serializeDevice(
+ deviceId: String,
+ permissionFlags: IndexedMap<String, Int>
+ ) {
+ tag(TAG_DEVICE) {
+ attributeInterned(ATTR_ID, deviceId)
+ permissionFlags.forEachIndexed { _, name, flags ->
+ serializePermission(name, flags)
+ }
+ }
+ }
+
+ private fun BinaryXmlSerializer.serializePermission(name: String, flags: Int) {
+ tag(TAG_PERMISSION) {
+ attributeInterned(ATTR_NAME, name)
+ // Never serialize one-time permissions as granted.
+ val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+ flags andInv PermissionFlags.RUNTIME_GRANTED
+ } else {
+ flags
+ }
+ attributeInt(ATTR_FLAGS, serializedFlags)
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = DevicePermissionPersistence::class.java.simpleName
+
+ private const val TAG_APP_ID_DEVICE_PERMISSIONS = "app-id-device-permissions"
+ private const val TAG_APP_ID = "app-id"
+ private const val TAG_DEVICE = "device"
+ private const val TAG_PERMISSION = "permission"
+
+ private const val ATTR_ID = "id"
+ private const val ATTR_NAME = "name"
+ private const val ATTR_FLAGS = "flags"
+ }
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
new file mode 100644
index 0000000..c0d7546
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -0,0 +1,276 @@
+/*
+ * 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.permission.access.permission
+
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.SchemePolicy
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
+import com.android.server.pm.pkg.PackageState
+
+class DevicePermissionPolicy : SchemePolicy() {
+ private val persistence = DevicePermissionPersistence()
+
+ @Volatile
+ private var listeners: IndexedListSet<OnDevicePermissionFlagsChangedListener> =
+ MutableIndexedListSet()
+ private val listenersLock = Any()
+
+ override val subjectScheme: String
+ get() = UidUri.SCHEME
+
+ override val objectScheme: String
+ get() = DevicePermissionUri.SCHEME
+
+ override fun GetStateScope.onStateMutated() {
+ listeners.forEachIndexed { _, it -> it.onStateMutated() }
+ }
+
+ override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+ newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+ if (appId in userState.appIdDevicePermissionFlags) {
+ newState.mutateUserStateAt(userStateIndex)
+ .mutateAppIdDevicePermissionFlags() -= appId
+ }
+ }
+ }
+
+ override fun MutateStateScope.onStorageVolumeMounted(
+ volumeUuid: String?,
+ packageNames: List<String>,
+ isSystemUpdated: Boolean
+ ) {
+ packageNames.forEachIndexed { _, packageName ->
+ val packageState = newState.externalState.packageStates[packageName]!!
+ trimPermissionStates(packageState.appId)
+ }
+ }
+
+ override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+ trimPermissionStates(packageState.appId)
+ }
+
+ override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
+ if (appId in newState.externalState.appIdPackageNames) {
+ trimPermissionStates(appId)
+ }
+ }
+
+ override fun MutateStateScope.onPackageUninstalled(
+ packageName: String,
+ appId: Int,
+ userId: Int
+ ) {
+ resetPermissionStates(packageName, userId)
+ }
+
+ private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) {
+ // It's okay to skip resetting permissions for packages that are removed,
+ // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
+ val packageState = newState.externalState.packageStates[packageName] ?: return
+ val androidPackage = packageState.androidPackage ?: return
+ val appId = packageState.appId
+ val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+ androidPackage.requestedPermissions.forEach { permissionName ->
+ val isRequestedByOtherPackages = anyPackageInAppId(appId) {
+ it.packageName != packageName &&
+ permissionName in it.androidPackage!!.requestedPermissions
+ }
+ if (isRequestedByOtherPackages) {
+ return@forEach
+ }
+ appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+ setPermissionFlags(appId, deviceId, userId, permissionName, 0)
+ }
+ }
+ }
+
+ private fun MutateStateScope.trimPermissionStates(appId: Int) {
+ val requestedPermissions = MutableIndexedSet<String>()
+ forEachPackageInAppId(appId) {
+ requestedPermissions += it.androidPackage!!.requestedPermissions
+ }
+ newState.userStates.forEachIndexed { _, userId, userState ->
+ userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed {
+ _, deviceId, permissionFlags ->
+ permissionFlags.forEachReversedIndexed { _, permissionName, _ ->
+ if (permissionName !in requestedPermissions) {
+ setPermissionFlags(appId, deviceId, userId, permissionName, 0)
+ }
+ }
+ }
+ }
+ }
+
+ private inline fun MutateStateScope.anyPackageInAppId(
+ appId: Int,
+ state: AccessState = newState,
+ predicate: (PackageState) -> Boolean
+ ): Boolean {
+ val packageNames = state.externalState.appIdPackageNames[appId]!!
+ return packageNames.anyIndexed { _, packageName ->
+ val packageState = state.externalState.packageStates[packageName]!!
+ packageState.androidPackage != null && predicate(packageState)
+ }
+ }
+
+ private inline fun MutateStateScope.forEachPackageInAppId(
+ appId: Int,
+ state: AccessState = newState,
+ action: (PackageState) -> Unit
+ ) {
+ val packageNames = state.externalState.appIdPackageNames[appId]!!
+ packageNames.forEachIndexed { _, packageName ->
+ val packageState = state.externalState.packageStates[packageName]!!
+ if (packageState.androidPackage != null) {
+ action(packageState)
+ }
+ }
+ }
+
+ override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+ with(persistence) { this@parseUserState.parseUserState(state, userId) }
+ }
+
+ override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+ with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
+ }
+
+ fun GetStateScope.getPermissionFlags(
+ appId: Int,
+ deviceId: String,
+ userId: Int,
+ permissionName: String
+ ): Int =
+ state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId)
+ ?.getWithDefault(permissionName, 0) ?: 0
+
+ fun MutateStateScope.setPermissionFlags(
+ appId: Int,
+ deviceId: String,
+ userId: Int,
+ permissionName: String,
+ flags: Int
+ ): Boolean =
+ updatePermissionFlags(
+ appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags
+ )
+
+ private fun MutateStateScope.updatePermissionFlags(
+ appId: Int,
+ deviceId: String,
+ userId: Int,
+ permissionName: String,
+ flagMask: Int,
+ flagValues: Int
+ ): Boolean {
+ if (!isDeviceAwarePermission(permissionName)) {
+ Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
+ return false
+ }
+ val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId]
+ ?.get(deviceId).getWithDefault(permissionName, 0)
+ val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
+ if (oldFlags == newFlags) {
+ return false
+ }
+ val appIdDevicePermissionFlags =
+ newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
+ val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) {
+ MutableIndexedReferenceMap()
+ }
+ val permissionFlags = devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() }
+ permissionFlags.putWithDefault(permissionName, newFlags, 0)
+ if (permissionFlags.isEmpty()) {
+ devicePermissionFlags -= deviceId
+ if (devicePermissionFlags.isEmpty()) {
+ appIdDevicePermissionFlags -= appId
+ }
+ }
+ listeners.forEachIndexed { _, it ->
+ it.onDevicePermissionFlagsChanged(
+ appId, userId, deviceId, permissionName, oldFlags, newFlags
+ )
+ }
+ return true
+ }
+
+ fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
+ synchronized(listenersLock) {
+ listeners = listeners + listener
+ }
+ }
+
+ fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
+ synchronized(listenersLock) {
+ listeners = listeners - listener
+ }
+ }
+
+ private fun isDeviceAwarePermission(permissionName: String): Boolean =
+ DEVICE_SUPPORTED_PERMISSIONS.contains(permissionName)
+
+ companion object {
+ private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
+
+ /**
+ * These permissions are supported for virtual devices.
+ */
+ private val DEVICE_SUPPORTED_PERMISSIONS = indexedSetOf(
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.RECORD_AUDIO
+ )
+ }
+
+ /**
+ * TODO: b/289355341 - implement listener for permission changes
+ * Listener for permission flags changes.
+ */
+ abstract class OnDevicePermissionFlagsChangedListener {
+ /**
+ * Called when a permission flags change has been made to the upcoming new state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation,
+ * and only call external code after [onStateMutated] when the new state has actually become
+ * the current state visible to external code.
+ */
+ abstract fun onDevicePermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ )
+
+ /**
+ * Called when the upcoming new state has become the current state.
+ *
+ * Implementations should keep this method fast to avoid stalling the locked state mutation.
+ */
+ abstract fun onStateMutated()
+ }
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index edacf188..d9f179a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -64,9 +64,11 @@
import com.android.server.PermissionThread
import com.android.server.ServiceThread
import com.android.server.SystemConfig
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
@@ -110,6 +112,9 @@
private val policy =
service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+ private val devicePolicy =
+ service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
+
private val context = service.context
private lateinit var metricsLogger: MetricsLogger
private lateinit var packageManagerInternal: PackageManagerInternal
@@ -132,6 +137,8 @@
private lateinit var permissionControllerManager: PermissionControllerManager
+ private lateinit var virtualDeviceManagerInternal: VirtualDeviceManagerInternal
+
/**
* A permission backup might contain apps that are not installed. In this case we delay the
* restoration until the app is installed.
@@ -152,6 +159,8 @@
systemConfig = SystemConfig.getInstance()
userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
userManagerService = UserManagerService.getInstance()
+ virtualDeviceManagerInternal =
+ LocalServices.getService(VirtualDeviceManagerInternal::class.java)
// The package info cache is the cache for package and permission information.
// Disable the package info and package permission caches locally but leave the
@@ -460,7 +469,7 @@
return size
}
- override fun checkUidPermission(uid: Int, permissionName: String): Int {
+ override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int {
val userId = UserHandle.getUserId(uid)
if (!userManagerInternal.exists(userId)) {
return PackageManager.PERMISSION_DENIED
@@ -482,7 +491,7 @@
return PackageManager.PERMISSION_DENIED
}
val isPermissionGranted = service.getState {
- isPermissionGranted(packageState, userId, permissionName)
+ isPermissionGranted(packageState, userId, permissionName, deviceId)
}
return if (isPermissionGranted) {
PackageManager.PERMISSION_GRANTED
@@ -515,7 +524,12 @@
return false
}
- override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+ override fun checkPermission(
+ packageName: String,
+ permissionName: String,
+ deviceId: Int,
+ userId: Int
+ ): Int {
if (!userManagerInternal.exists(userId)) {
return PackageManager.PERMISSION_DENIED
}
@@ -524,7 +538,7 @@
.use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
val isPermissionGranted = service.getState {
- isPermissionGranted(packageState, userId, permissionName)
+ isPermissionGranted(packageState, userId, permissionName, deviceId)
}
return if (isPermissionGranted) {
PackageManager.PERMISSION_GRANTED
@@ -542,19 +556,21 @@
private fun GetStateScope.isPermissionGranted(
packageState: PackageState,
userId: Int,
- permissionName: String
+ permissionName: String,
+ deviceId: Int
): Boolean {
val appId = packageState.appId
// Note that instant apps can't have shared UIDs, so we only need to check the current
// package state.
val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp
- if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) {
+ if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) {
return true
}
val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
if (fullerPermissionName != null &&
- isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) {
+ isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId)
+ ) {
return true
}
@@ -568,9 +584,10 @@
appId: Int,
userId: Int,
isInstantApp: Boolean,
- permissionName: String
+ permissionName: String,
+ deviceId: Int,
): Boolean {
- val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
if (!PermissionFlags.isPermissionGranted(flags)) {
return false
}
@@ -601,7 +618,8 @@
?: return emptySet()
return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
- if (isPermissionGranted(packageState, userId, permissionName)) {
+ if (isPermissionGranted(
+ packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) {
permissionName
} else {
null
@@ -640,18 +658,26 @@
}
}
- override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
- setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true)
+ override fun grantRuntimePermission(
+ packageName: String,
+ permissionName: String,
+ deviceId: Int,
+ userId: Int
+ ) {
+ setRuntimePermissionGranted(
+ packageName, userId, permissionName, deviceId, isGranted = true
+ )
}
override fun revokeRuntimePermission(
packageName: String,
permissionName: String,
+ deviceId: Int,
userId: Int,
reason: String?
) {
setRuntimePermissionGranted(
- packageName, userId, permissionName, isGranted = false, revokeReason = reason
+ packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason
)
}
@@ -660,8 +686,8 @@
userId: Int
) {
setRuntimePermissionGranted(
- packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false,
- skipKillUid = true
+ packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT,
+ isGranted = false, skipKillUid = true
)
}
@@ -673,6 +699,7 @@
packageName: String,
userId: Int,
permissionName: String,
+ deviceId: Int,
isGranted: Boolean,
skipKillUid: Boolean = false,
revokeReason: String? = null
@@ -748,7 +775,7 @@
}
setRuntimePermissionGranted(
- packageState, userId, permissionName, isGranted, canManageRolePermission,
+ packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission,
overridePolicyFixed, reportError = true, methodName
)
}
@@ -782,14 +809,16 @@
if (permissionState ==
PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
setRuntimePermissionGranted(
- packageState, userId, permissionName, isGranted = true,
- canManageRolePermission = false, overridePolicyFixed = false,
- reportError = false, "setRequestedPermissionStates"
+ packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT,
+ isGranted = true, canManageRolePermission = false,
+ overridePolicyFixed = false, reportError = false,
+ "setRequestedPermissionStates"
)
updatePermissionFlags(
packageState.appId, userId, permissionName,
+ Context.DEVICE_ID_DEFAULT,
PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
- PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
canUpdateSystemFlags = false,
reportErrorForUnknownPermission = false,
isPermissionRequested = true, "setRequestedPermissionStates",
@@ -816,6 +845,7 @@
packageState: PackageState,
userId: Int,
permissionName: String,
+ deviceId: Int,
isGranted: Boolean,
canManageRolePermission: Boolean,
overridePolicyFixed: Boolean,
@@ -871,7 +901,7 @@
}
val appId = packageState.appId
- val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
if (reportError) {
@@ -934,7 +964,7 @@
return
}
- with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+ setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
if (permission.isRuntime) {
val action = if (isGranted) {
@@ -963,7 +993,12 @@
with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
}
- override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
+ override fun getPermissionFlags(
+ packageName: String,
+ permissionName: String,
+ deviceId: Int,
+ userId: Int,
+ ): Int {
if (!userManagerInternal.exists(userId)) {
Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
return 0
@@ -994,7 +1029,8 @@
}
val flags =
- with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+ getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
+
return PermissionFlags.toApiFlags(flags)
}
}
@@ -1002,6 +1038,7 @@
override fun isPermissionRevokedByPolicy(
packageName: String,
permissionName: String,
+ deviceId: Int,
userId: Int
): Boolean {
if (!userManagerInternal.exists(userId)) {
@@ -1018,13 +1055,13 @@
.use { it.getPackageState(packageName) } ?: return false
service.getState {
- if (isPermissionGranted(packageState, userId, permissionName)) {
+ if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
return false
}
- val flags = with(policy) {
- getPermissionFlags(packageState.appId, userId, permissionName)
- }
+ val flags =
+ getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
+
return flags.hasBits(PermissionFlags.POLICY_FIXED)
}
}
@@ -1046,7 +1083,8 @@
override fun shouldShowRequestPermissionRationale(
packageName: String,
permissionName: String,
- userId: Int
+ deviceId: Int,
+ userId: Int,
): Boolean {
if (!userManagerInternal.exists(userId)) {
Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
@@ -1068,11 +1106,11 @@
val flags: Int
service.getState {
- if (isPermissionGranted(packageState, userId, permissionName)) {
+ if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
return false
}
- flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
}
if (flags.hasAnyBit(UNREQUESTABLE_MASK)) {
return false
@@ -1104,6 +1142,7 @@
flagMask: Int,
flagValues: Int,
enforceAdjustPolicyPermission: Boolean,
+ deviceId: Int,
userId: Int
) {
val callingUid = Binder.getCallingUid()
@@ -1199,7 +1238,7 @@
val appId = packageState.appId
service.mutateState {
updatePermissionFlags(
- appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags,
+ appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags,
reportErrorForUnknownPermission = true, isPermissionRequested,
"updatePermissionFlags", packageName
)
@@ -1248,8 +1287,9 @@
val androidPackage = packageState.androidPackage ?: return@forEach
androidPackage.requestedPermissions.forEach { permissionName ->
updatePermissionFlags(
- packageState.appId, userId, permissionName, flagMask, flagValues,
- canUpdateSystemFlags, reportErrorForUnknownPermission = false,
+ packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT,
+ flagMask, flagValues, canUpdateSystemFlags,
+ reportErrorForUnknownPermission = false,
isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
)
}
@@ -1264,6 +1304,7 @@
appId: Int,
userId: Int,
permissionName: String,
+ deviceId: Int,
flagMask: Int,
flagValues: Int,
canUpdateSystemFlags: Boolean,
@@ -1298,7 +1339,7 @@
return
}
- val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
if (!isPermissionRequested && oldFlags == 0) {
Slog.w(
LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
@@ -1308,7 +1349,7 @@
}
val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues)
- with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+ setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
}
override fun getAllowlistedRestrictedPermissions(
@@ -1365,6 +1406,49 @@
)
}
+ private fun GetStateScope.getPermissionFlagsWithPolicy(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ deviceId: Int,
+ ): Int =
+ if (deviceId == Context.DEVICE_ID_DEFAULT) {
+ with(policy) { getPermissionFlags(appId, userId, permissionName) }
+ } else {
+ val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+ if (persistentDeviceId != null) {
+ with(devicePolicy) {
+ getPermissionFlags(appId, persistentDeviceId, userId, permissionName)
+ }
+ } else {
+ Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no persistent device ID found.")
+ 0
+ }
+ }
+
+ private fun MutateStateScope.setPermissionFlagsWithPolicy(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ deviceId: Int,
+ flags: Int
+ ): Boolean =
+ if (deviceId == Context.DEVICE_ID_DEFAULT) {
+ with(policy) {
+ setPermissionFlags(appId, userId, permissionName, flags)
+ }
+ } else {
+ val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+ if (persistentDeviceId != null) {
+ with(devicePolicy) {
+ setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags)
+ }
+ } else {
+ Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no cdm association found.")
+ false
+ }
+ }
+
/**
* This method does not enforce checks on the caller, should only be called after
* required checks.
@@ -1539,8 +1623,7 @@
) {
service.mutateState {
with(policy) {
- val permissionsFlags =
- getUidPermissionFlags(appId, userId) ?: return@mutateState
+ val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
val permissions = getPermissions()
androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
@@ -1661,8 +1744,6 @@
)
}
-
-
override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
requireNotNull(permissionName) { "permissionName cannot be null" }
val packageNames = ArraySet<String>()
@@ -1879,7 +1960,7 @@
println("Permissions:")
withIndent {
userState.appIdPermissionFlags[appId]?.forEachIndexed {
- _, permissionName, flags ->
+ _, permissionName, flags ->
val isGranted = PermissionFlags.isPermissionGranted(flags)
println(
"$permissionName: granted=$isGranted, flags=" +
@@ -1888,6 +1969,20 @@
}
}
+ userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
+ _, deviceId, devicePermissionFlags ->
+ println("Permissions (Device $deviceId):")
+ withIndent {
+ devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
+ val isGranted = PermissionFlags.isPermissionGranted(flags)
+ println(
+ "$permissionName: granted=$isGranted, flags=" +
+ PermissionFlags.toString(flags)
+ )
+ }
+ }
+ }
+
println("App ops:")
withIndent {
userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
@@ -2412,7 +2507,7 @@
}
private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
- if (checkUidPermission(uid, Manifest.permission.BACKUP) !=
+ if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
PackageManager.PERMISSION_GRANTED) {
return false
}
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 35b9bc3..4a8d73d2 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -254,12 +254,45 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- return userState.getCustomPrinterIcon(printerId);
+ Icon icon = userState.getCustomPrinterIcon(printerId);
+ return validateIconUserBoundary(icon);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
+ /**
+ * Validates the custom printer icon to see if it's not in the calling user space.
+ * If the condition is not met, return null. Otherwise, return the original icon.
+ *
+ * @param icon
+ * @return icon (validated)
+ */
+ private Icon validateIconUserBoundary(Icon icon) {
+ // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+ // incompatible types.
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+ String encodedUser = icon.getUri().getEncodedUserInfo();
+
+ // If there is no encoded user, the URI is calling into the calling user space
+ if (encodedUser != null) {
+ int userId = Integer.parseInt(encodedUser);
+ // resolve encoded user
+ final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+
+ synchronized (mLock) {
+ // Only the current group members can get the printer icons.
+ if (resolveCallingProfileParentLocked(resolvedUserId)
+ != getCurrentUserId()) {
+ return null;
+ }
+ }
+ }
+ }
+ return icon;
+ }
+
@Override
public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) {
if (printJobId == null) {
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
index b698a60..8ef443e 100644
--- a/services/tests/dreamservicetests/Android.bp
+++ b/services/tests/dreamservicetests/Android.bp
@@ -16,6 +16,7 @@
"frameworks-base-testutils",
"mockito-target-minus-junit4",
"services.core",
+ "mockingservicestests-utils-mockito",
],
platform_apis: true,
diff --git a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
similarity index 97%
rename from services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
rename to services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
index c02cbd1..32d4e75 100644
--- a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -36,6 +36,7 @@
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -102,6 +103,7 @@
}
@Test
+ @FlakyTest(bugId = 293443309)
public void testSettingsQueryUserChange() {
final DreamManagerService service = createService();
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 1f4563f..976e740 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -105,6 +105,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowProcessController;
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -206,8 +207,10 @@
setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
.enqueueProcessChangeItemLocked(anyInt(), anyInt());
- sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
- new ActiveUids(sService, false));
+ sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ
+ ? new OomAdjusterModernImpl(sService, sService.mProcessList,
+ new ActiveUids(sService, false))
+ : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false));
sService.mOomAdjuster.mAdjSeq = 10000;
sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
@@ -220,6 +223,11 @@
LocalServices.removeServiceForTest(PackageManagerInternal.class);
}
+ @After
+ public void tearDown() {
+ sService.mOomAdjuster.resetInternal();
+ }
+
private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
try {
Field field = clazz.getDeclaredField(fieldName);
@@ -249,6 +257,9 @@
ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
lru.clear();
Collections.addAll(lru, apps);
+ for (ProcessRecord app : apps) {
+ sService.mOomAdjuster.onProcessBeginLocked(app);
+ }
}
/**
@@ -259,6 +270,7 @@
@SuppressWarnings("GuardedBy")
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 1) {
+ sService.mOomAdjuster.onProcessBeginLocked(apps[0]);
sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
@@ -600,10 +612,13 @@
s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+ sService.mOomAdjuster.onProcessBeginLocked(system);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
// Out of grace period but valid binding allows the adjustment.
@@ -620,10 +635,13 @@
s.lastTopAlmostPerceptibleBindRequestUptimeMs =
nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+ sService.mOomAdjuster.onProcessBeginLocked(system);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
// Out of grace period and no valid binding so no adjustment.
@@ -641,10 +659,13 @@
nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
s.getConnections().clear();
app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+ sService.mOomAdjuster.onProcessBeginLocked(system);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
}
@@ -657,11 +678,12 @@
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
system.mState.setHasTopUi(true);
+ sService.mOomAdjuster.onProcessBeginLocked(system);
// Simulate the system starting and binding to a service in the app.
ServiceRecord s = bindService(app, system,
null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ updateOomAdj(system, app);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -850,6 +872,7 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+ client.mServices.setTreatLikeActivity(true);
bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
| Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -1006,7 +1029,7 @@
bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- updateOomAdj(app);
+ updateOomAdj(client, app);
assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
assertNoBfsl(app);
@@ -1132,7 +1155,7 @@
assertNoBfsl(app);
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
- updateOomAdj(app);
+ updateOomAdj(client, app);
assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
assertBfsl(app);
@@ -1148,7 +1171,7 @@
bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
client.mState.setRunningRemoteAnimation(true);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- updateOomAdj(app);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
}
@@ -1199,6 +1222,8 @@
updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
{
@@ -1217,6 +1242,8 @@
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
{
@@ -1229,9 +1256,11 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
{
@@ -1246,10 +1275,12 @@
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ updateOomAdj(client, app);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+ sService.mOomAdjuster.resetInternal();
}
}
@@ -1849,7 +1880,7 @@
bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
- updateOomAdj(app1, app2);
+ updateOomAdj(client1, client2, app1, app2);
assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
SCHED_GROUP_TOP_APP);
@@ -1899,6 +1930,8 @@
s1.getConnections().clear();
s2.getConnections().clear();
+ client1.mServices.removeAllConnections();
+ client2.mServices.removeAllConnections();
client1.mState.setMaxAdj(UNKNOWN_ADJ);
client2.mState.setMaxAdj(UNKNOWN_ADJ);
client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
@@ -1909,7 +1942,7 @@
bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
mock(IBinder.class));
- updateOomAdj(app1, app2);
+ updateOomAdj(client1, client2, app1, app2);
// VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -1922,7 +1955,7 @@
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
+ updateOomAdj(client2, app2);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
}
@@ -1977,6 +2010,7 @@
app.setPendingFinishAttach(true);
app.mState.setHasForegroundActivities(false);
+ sService.mOomAdjuster.onProcessBeginLocked(app);
sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
@@ -1991,7 +2025,9 @@
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
app.setPendingFinishAttach(true);
app.mState.setHasForegroundActivities(true);
+ doReturn(app).when(sService).getTopApp();
+ sService.mOomAdjuster.onProcessBeginLocked(app);
sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
updateOomAdj(app);
@@ -2088,7 +2124,7 @@
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
doNothing().when(sService.mServices)
.scheduleServiceTimeoutLocked(any(ProcessRecord.class));
- sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
+ updateOomAdj(client1, client2, app1, app2, app3);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2426,6 +2462,8 @@
lru.clear();
lru.add(app2);
lru.add(app);
+ sService.mOomAdjuster.onProcessBeginLocked(app2);
+ sService.mOomAdjuster.onProcessBeginLocked(app);
final ComponentName cn = ComponentName.unflattenFromString(
MOCKAPP_PACKAGENAME + "/.TestService");
@@ -2528,7 +2566,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());
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index cd3a78e..6906dec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2157,6 +2157,14 @@
}
@Test
+ public void testResetGamePowerMode() {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.onBootCompleted();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false);
+ }
+
+ @Test
public void testNotifyGraphicsEnvironmentSetup() {
String configString = "mode=2,loadingBoost=2000";
when(DeviceConfig.getProperty(anyString(), anyString()))
diff --git a/services/tests/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log b/services/tests/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log
new file mode 100644
index 0000000..ee331c2
--- /dev/null
+++ b/services/tests/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log
@@ -0,0 +1,29 @@
+ * Gesture6_id30:Swipe down with 3 finger
+MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=5273700, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_DOWN(1), actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273700, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273709, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_DOWN(2), actionButton=0, id[0]=0, x[0]=150.0, y[0]=696.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=100.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273709, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=100.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273715, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=60.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=80.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=110.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273725, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=70.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=90.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=120.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273734, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=80.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=100.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=130.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273741, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=90.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=110.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=140.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273750, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=100.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=120.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=150.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273758, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=110.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=130.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=160.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273767, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=120.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=140.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=170.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273776, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=130.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=150.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=180.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273784, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=140.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=160.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=190.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273793, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=150.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=170.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=200.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273801, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=160.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=180.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=210.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273810, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=170.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=190.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=220.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273822, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=180.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=200.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=230.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273831, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=190.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=210.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=240.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273836, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=200.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=220.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=250.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273844, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=210.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=230.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=260.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273853, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=220.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=240.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=270.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273862, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=230.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=250.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=280.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273870, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=240.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=260.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=290.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273879, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=250.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=270.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=300.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273888, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_UP(0), actionButton=0, id[0]=0, x[0]=150.0, y[0]=250.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=270.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=300.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_UP(0), actionButton=0, id[0]=1, x[0]=216.0, y[0]=270.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=2, x[1]=94.0, y[1]=300.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_UP, actionButton=0, id[0]=2, x[0]=94.0, y[0]=300.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
new file mode 100644
index 0000000..008a53f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
new file mode 100644
index 0000000..01159b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.companion.PackageUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class PackageUtilsTest {
+ private static final String[] ALLOWED_PACKAGE_NAMES = new String[]{
+ "allowed_app",
+ };
+ private static final Signature[] ALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+ new Signature("001122"),
+ };
+ private static final String[] DISALLOWED_PACKAGE_NAMES = new String[]{
+ "disallowed_app",
+ };
+ private static final Signature[] DISALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+ new Signature("778899"),
+ };
+
+ @Test
+ public void isAllowlisted_true() {
+ Context context = spy(
+ new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ final Resources res = spy(context.getResources());
+ doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDevicePackages);
+ doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+ ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDeviceCerts);
+ doReturn(res).when(context).getResources();
+ PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ AndroidPackage ap = mock(AndroidPackage.class);
+ SigningDetails sd = new SigningDetails(
+ ALLOWED_PACKAGE_SIGNATURES,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null);
+ doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+ doReturn(sd).when(ap).getSigningDetails();
+ assertTrue(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+ }
+
+ @Test
+ public void isAllowlisted_package_disallowed() {
+ Context context = spy(
+ new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ final Resources res = spy(context.getResources());
+ doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDevicePackages);
+ doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+ ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDeviceCerts);
+ doReturn(res).when(context).getResources();
+ PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ AndroidPackage ap = mock(AndroidPackage.class);
+ SigningDetails sd = new SigningDetails(
+ ALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null);
+ doReturn(ap).when(pm).getPackage(DISALLOWED_PACKAGE_NAMES[0]);
+ doReturn(sd).when(ap).getSigningDetails();
+ assertFalse(PackageUtils.isPackageAllowlisted(context, pm, DISALLOWED_PACKAGE_NAMES[0]));
+ }
+
+ @Test
+ public void isAllowlisted_signature_mismatch() {
+ Context context = spy(
+ new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ final Resources res = spy(context.getResources());
+ doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDevicePackages);
+ doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+ ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+ com.android.internal.R.array.config_companionDeviceCerts);
+ doReturn(res).when(context).getResources();
+ PackageManagerInternal pm = mock(PackageManagerInternal.class);
+ AndroidPackage ap = mock(AndroidPackage.class);
+ SigningDetails sd = new SigningDetails(
+ DISALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+ null,
+ null);
+ doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+ doReturn(sd).when(ap).getSigningDetails();
+ assertFalse(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
index 5e7dc33..1726ec1 100644
--- a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
@@ -24,6 +24,8 @@
import android.os.DeviceIdleManager;
import android.test.AndroidTestCase;
+import androidx.test.filters.FlakyTest;
+
import com.android.server.job.MockBiasJobService.TestEnvironment;
import com.android.server.job.MockBiasJobService.TestEnvironment.Event;
@@ -58,6 +60,7 @@
super.tearDown();
}
+ @FlakyTest(bugId = 293589359)
public void testLowerBiasJobPreempted() throws Exception {
for (int i = 0; i < JobConcurrencyManager.MAX_CONCURRENCY_LIMIT; ++i) {
JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index cc1100b..e5909a4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -36,6 +36,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -297,13 +298,15 @@
TestData.getInsecureCertPathForEndpoint1());
addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
+ setExpectedScryptArgument(password.getBytes());
+
mKeySyncTask.run();
KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
isEqualTo(UI_FORMAT_PASSWORD);
- verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+ verify(mMockScrypt).scrypt(any(), any(),
eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
KeyDerivationParams keyDerivationParams =
@@ -314,6 +317,44 @@
}
@Test
+ public void run_zeroizedCredential() throws Exception {
+ String password = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "123";
+ String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
+ byte[] zeroizedCredential = password.getBytes();
+ mKeySyncTask = new KeySyncTask(
+ mRecoverableKeyStoreDb,
+ mRecoverySnapshotStorage,
+ mSnapshotListenersStorage,
+ TEST_USER_ID,
+ CREDENTIAL_TYPE_PASSWORD,
+ /*credential=*/ zeroizedCredential,
+ /*credentialUpdated=*/ false,
+ mPlatformKeyManager,
+ mTestOnlyInsecureCertificateHelper,
+ mMockScrypt);
+ mRecoverableKeyStoreDb.setServerParams(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
+ mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+ mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+ TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
+ mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
+ TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+ TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
+ TestData.getInsecureCertPathForEndpoint1());
+ addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
+
+ // Need to check array value during method call since it is modified later.
+ setExpectedScryptArgument(password.getBytes());
+
+ Arrays.fill(zeroizedCredential, (byte) 0);
+ mKeySyncTask.run();
+
+ verify(mMockScrypt).scrypt(any(), any(),
+ eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+ eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+ }
+
+ @Test
public void run_useSha256ToHashPatternInProdMode() throws Exception {
String pattern = "123456";
mKeySyncTask = new KeySyncTask(
@@ -368,13 +409,15 @@
mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
+ setExpectedScryptArgument(shortPassword.getBytes());
+
mKeySyncTask.run();
KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
isEqualTo(UI_FORMAT_PASSWORD);
- verify(mMockScrypt).scrypt(eq(shortPassword.getBytes()), any(),
+ verify(mMockScrypt).scrypt(any(), any(),
eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
KeyDerivationParams keyDerivationParams =
@@ -650,13 +693,15 @@
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+ setExpectedScryptArgument(password.getBytes());
+
mKeySyncTask.run();
KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
isEqualTo(UI_FORMAT_PASSWORD);
- verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+ verify(mMockScrypt).scrypt(any(), any(),
eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
}
@@ -681,6 +726,8 @@
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
+ setExpectedScryptArgument(pin.getBytes());
+
mKeySyncTask.run();
KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
@@ -688,7 +735,7 @@
// Password with only digits is changed to pin.
assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
isEqualTo(UI_FORMAT_PIN);
- verify(mMockScrypt).scrypt(eq(pin.getBytes()), any(),
+ verify(mMockScrypt).scrypt(any(), any(),
eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
}
@@ -868,4 +915,14 @@
new Random().nextBytes(bytes);
return bytes;
}
+
+ private void setExpectedScryptArgument(byte[] credentials) {
+ doAnswer(invocation -> {
+ assertThat((byte[]) invocation.getArguments()[0]).isEqualTo(credentials);
+ return invocation.callRealMethod();
+ }).when(mMockScrypt).scrypt(any(), any(),
+ eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+ eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index bb8b986..ddd1221 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -62,6 +62,7 @@
import android.view.ContentRecordingSession;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -688,6 +689,7 @@
assertThat(mService.isCurrentProjection(projection)).isTrue();
}
+ @FlakyTest(bugId = 288342281)
@Test
public void setContentRecordingSession_successful_notifiesListeners()
throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 8c07b6c..f834cb2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -52,7 +52,6 @@
factory.isNamespaceAware = true
factory.newPullParser()
}
- private val ns = "xmlns:android=\"http://schemas.android.com/apk/res/android\""
}
@Test
@@ -100,19 +99,45 @@
@Test
fun parseApplicationTag() {
val tag = "application"
- validateTagAttr(tag, "backupAgent",
- R.styleable.AndroidManifestApplication_backupAgent, 1024)
- validateTagAttr(tag, "manageSpaceActivity",
- R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024)
+ validateTagAttr(
+ tag,
+ "backupAgent",
+ R.styleable.AndroidManifestApplication_backupAgent,
+ 1024
+ )
+ validateTagAttrComponentName(
+ tag,
+ "backupAgent",
+ R.styleable.AndroidManifestApplication_backupAgent
+ )
+ validateTagAttr(
+ tag,
+ "manageSpaceActivity",
+ R.styleable.AndroidManifestApplication_manageSpaceActivity,
+ 1024
+ )
validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestApplication_name)
validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024)
validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024)
- validateTagAttr(tag, "requiredAccountType",
- R.styleable.AndroidManifestApplication_requiredAccountType, 1024)
- validateTagAttr(tag, "restrictedAccountType",
- R.styleable.AndroidManifestApplication_restrictedAccountType, 1024)
- validateTagAttr(tag, "taskAffinity",
- R.styleable.AndroidManifestApplication_taskAffinity, 1024)
+ validateTagAttr(
+ tag,
+ "requiredAccountType",
+ R.styleable.AndroidManifestApplication_requiredAccountType,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "restrictedAccountType",
+ R.styleable.AndroidManifestApplication_restrictedAccountType,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "taskAffinity",
+ R.styleable.AndroidManifestApplication_taskAffinity,
+ 1024
+ )
validateTagCount("profileable", 100, tag)
validateTagCount("uses-native-library", 100, tag)
validateTagCount("receiver", 1000, tag)
@@ -134,6 +159,7 @@
fun parseReceiverTag() {
val tag = "receiver"
validateTagAttr(tag, "name", R.styleable.AndroidManifestReceiver_name, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestReceiver_name)
validateTagAttr(tag, "permission", R.styleable.AndroidManifestReceiver_permission, 1024)
validateTagAttr(tag, "process", R.styleable.AndroidManifestReceiver_process, 1024)
validateTagCount("meta-data", 1000, tag)
@@ -144,6 +170,7 @@
fun parseServiceTag() {
val tag = "service"
validateTagAttr(tag, "name", R.styleable.AndroidManifestService_name, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestService_name)
validateTagAttr(tag, "permission", R.styleable.AndroidManifestService_permission, 1024)
validateTagAttr(tag, "process", R.styleable.AndroidManifestService_process, 1024)
validateTagCount("meta-data", 1000, tag)
@@ -154,10 +181,23 @@
fun parseActivityAliasTag() {
val tag = "activity-alias"
validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024)
- validateTagAttr(tag, "permission",
- R.styleable.AndroidManifestActivityAlias_permission, 1024)
- validateTagAttr(tag, "targetActivity",
- R.styleable.AndroidManifestActivityAlias_targetActivity, 1024)
+ validateTagAttr(
+ tag,
+ "permission",
+ R.styleable.AndroidManifestActivityAlias_permission,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "targetActivity",
+ R.styleable.AndroidManifestActivityAlias_targetActivity,
+ 1024
+ )
+ validateTagAttrComponentName(
+ tag,
+ "targetActivity",
+ R.styleable.AndroidManifestActivityAlias_targetActivity
+ )
validateTagCount("meta-data", 1000, tag)
validateTagCount("intent-filter", 20000, tag)
}
@@ -172,8 +212,18 @@
fun parseActivityTag() {
val tag = "activity"
validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024)
- validateTagAttr(tag, "parentActivityName",
- R.styleable.AndroidManifestActivity_parentActivityName, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestActivity_name)
+ validateTagAttr(
+ tag,
+ "parentActivityName",
+ R.styleable.AndroidManifestActivity_parentActivityName,
+ 1024
+ )
+ validateTagAttrComponentName(
+ tag,
+ "parentActivityName",
+ R.styleable.AndroidManifestActivity_parentActivityName
+ )
validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024)
validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024)
validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024)
@@ -186,24 +236,49 @@
fun parseOverlayTag() {
val tag = "overlay"
validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024)
- validateTagAttr(tag, "requiredSystemPropertyName",
- R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024)
- validateTagAttr(tag, "requiredSystemPropertyValue",
- R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX)
- validateTagAttr(tag, "targetPackage",
- R.styleable.AndroidManifestResourceOverlay_targetPackage, 256)
- validateTagAttr(tag, "targetName",
- R.styleable.AndroidManifestResourceOverlay_targetName, 1024)
+ validateTagAttr(
+ tag,
+ "requiredSystemPropertyName",
+ R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "requiredSystemPropertyValue",
+ R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue,
+ PROP_VALUE_MAX
+ )
+ validateTagAttr(
+ tag,
+ "targetPackage",
+ R.styleable.AndroidManifestResourceOverlay_targetPackage,
+ 256
+ )
+ validateTagAttr(
+ tag,
+ "targetName",
+ R.styleable.AndroidManifestResourceOverlay_targetName,
+ 1024
+ )
}
@Test
fun parseInstrumentationTag() {
val tag = "instrumentation"
validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024)
- validateTagAttr(tag, "targetPackage",
- R.styleable.AndroidManifestInstrumentation_targetPackage, 256)
- validateTagAttr(tag, "targetProcesses",
- R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestInstrumentation_name)
+ validateTagAttr(
+ tag,
+ "targetPackage",
+ R.styleable.AndroidManifestInstrumentation_targetPackage,
+ 256
+ )
+ validateTagAttr(
+ tag,
+ "targetProcesses",
+ R.styleable.AndroidManifestInstrumentation_targetProcesses,
+ 1024
+ )
}
@Test
@@ -262,12 +337,21 @@
fun parseProviderTag() {
val tag = "provider"
validateTagAttr(tag, "name", R.styleable.AndroidManifestProvider_name, 1024)
+ validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestProvider_name)
validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024)
validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024)
- validateTagAttr(tag, "readPermission",
- R.styleable.AndroidManifestProvider_readPermission, 1024)
- validateTagAttr(tag, "writePermission",
- R.styleable.AndroidManifestProvider_writePermission, 1024)
+ validateTagAttr(
+ tag,
+ "readPermission",
+ R.styleable.AndroidManifestProvider_readPermission,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "writePermission",
+ R.styleable.AndroidManifestProvider_writePermission,
+ 1024
+ )
validateTagCount("grant-uri-permission", 100, tag)
validateTagCount("path-permission", 100, tag)
validateTagCount("meta-data", 1000, tag)
@@ -278,26 +362,54 @@
fun parseGrantUriPermissionTag() {
val tag = "grant-uri-permission"
validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000)
- validateTagAttr(tag, "pathPrefix",
- R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000)
- validateTagAttr(tag, "pathPattern",
- R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000)
+ validateTagAttr(
+ tag,
+ "pathPrefix",
+ R.styleable.AndroidManifestGrantUriPermission_pathPrefix,
+ 4000
+ )
+ validateTagAttr(
+ tag,
+ "pathPattern",
+ R.styleable.AndroidManifestGrantUriPermission_pathPattern,
+ 4000
+ )
}
@Test
fun parsePathPermissionTag() {
val tag = "path-permission"
validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000)
- validateTagAttr(tag, "pathPrefix",
- R.styleable.AndroidManifestPathPermission_pathPrefix, 4000)
- validateTagAttr(tag, "pathPattern",
- R.styleable.AndroidManifestPathPermission_pathPattern, 4000)
- validateTagAttr(tag, "permission",
- R.styleable.AndroidManifestPathPermission_permission, 1024)
- validateTagAttr(tag, "readPermission",
- R.styleable.AndroidManifestPathPermission_readPermission, 1024)
- validateTagAttr(tag, "writePermission",
- R.styleable.AndroidManifestPathPermission_writePermission, 1024)
+ validateTagAttr(
+ tag,
+ "pathPrefix",
+ R.styleable.AndroidManifestPathPermission_pathPrefix,
+ 4000
+ )
+ validateTagAttr(
+ tag,
+ "pathPattern",
+ R.styleable.AndroidManifestPathPermission_pathPattern,
+ 4000
+ )
+ validateTagAttr(
+ tag,
+ "permission",
+ R.styleable.AndroidManifestPathPermission_permission,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "readPermission",
+ R.styleable.AndroidManifestPathPermission_readPermission,
+ 1024
+ )
+ validateTagAttr(
+ tag,
+ "writePermission",
+ R.styleable.AndroidManifestPathPermission_writePermission,
+ 1024
+ )
}
@Test
@@ -336,8 +448,12 @@
validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000)
validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000)
validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000)
- validateTagAttr(tag, "pathAdvancedPattern",
- R.styleable.AndroidManifestData_pathAdvancedPattern, 4000)
+ validateTagAttr(
+ tag,
+ "pathAdvancedPattern",
+ R.styleable.AndroidManifestData_pathAdvancedPattern,
+ 4000
+ )
validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
}
@@ -351,8 +467,12 @@
fun parsePermissionTag() {
val tag = "permission"
validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024)
- validateTagAttr(tag, "permissionGroup",
- R.styleable.AndroidManifestPermission_permissionGroup, 256)
+ validateTagAttr(
+ tag,
+ "permissionGroup",
+ R.styleable.AndroidManifestPermission_permissionGroup,
+ 256
+ )
}
@Test
@@ -361,6 +481,56 @@
validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
}
+ private fun validateTagAttrComponentName(tag: String, attr: String, index: Int) {
+ val passNames = arrayOf("com.android.TestClass", "TestClass", "_", "$", ".TestClass", "上")
+ for (name in passNames) {
+ val xml = "<$tag $attr=\"$name\" />"
+ pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+ val validator = Validator()
+ pullParser.nextTag()
+ validator.validate(pullParser)
+ try {
+ validator.validateStrAttr(pullParser, attr, name)
+ } catch (e: SecurityException) {
+ fail(
+ "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+ " ${e.message}"
+ )
+ }
+ try {
+ validator.validateResStrAttr(pullParser, index, name)
+ } catch (e: SecurityException) {
+ fail(
+ "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+ " ${e.message}"
+ )
+ }
+ }
+
+ val failNames = arrayOf("com.android.TestClass:", "-TestClass", "TestClass.", ".", "..")
+ for (name in failNames) {
+ val xml = "<$tag $attr=\"$name\" />"
+ pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+ val validator = Validator()
+ pullParser.nextTag()
+ validator.validate(pullParser)
+ val e1 = assertThrows(
+ "$name is not valid Java class name",
+ SecurityException::class.java
+ ) {
+ validator.validateStrAttr(pullParser, attr, name)
+ }
+ assertEquals(expectedAttrComponentNameErrorMsg(name), e1.message)
+ val e2 = assertThrows(
+ "$name is not valid Java class name",
+ SecurityException::class.java
+ ) {
+ validator.validateResStrAttr(pullParser, index, name)
+ }
+ assertEquals(expectedAttrComponentNameErrorMsg(name), e2.message)
+ }
+ }
+
private fun validateTagAttr(tag: String, name: String, index: Int?, maxLen: Int) {
validateTagAttr_shouldPass(tag, name, index, maxLen)
validateTagAttr_shouldFail(tag, name, index, maxLen)
@@ -381,15 +551,19 @@
try {
validator.validateStrAttr(pullParser, name, value)
} catch (e: SecurityException) {
- fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
- " ${e.message}")
+ fail(
+ "Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
+ " ${e.message}"
+ )
}
if (index != null) {
try {
validator.validateResStrAttr(pullParser, index, value)
} catch (e: SecurityException) {
- fail("Failed to parse valid <$tag> resource string attribute $name with max" +
- " length of $maxLen: ${e.message}")
+ fail(
+ "Failed to parse valid <$tag> resource string attribute $name with max" +
+ " length of $maxLen: ${e.message}"
+ )
}
}
}
@@ -429,8 +603,10 @@
try {
parseXmlStr(xml)
} catch (e: SecurityException) {
- fail("Failed to parse <$tag> with max count limit of $maxNum under" +
- " <$parentTag>: ${e.message}")
+ fail(
+ "Failed to parse <$tag> with max count limit of $maxNum under" +
+ " <$parentTag>: ${e.message}"
+ )
}
}
@@ -468,4 +644,6 @@
fun expectedResAttrLengthErrorMsg(tag: String) =
"String length limit exceeded for attribute in $tag"
+
+ fun expectedAttrComponentNameErrorMsg(name: String) = "$name is not a valid Java class name"
}
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
index 22b72fa..f0a7e47 100644
--- a/services/tests/vibrator/TEST_MAPPING
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -1,7 +1,21 @@
{
- "imports": [
+ "presubmit": [
{
- "path": "frameworks/base/services/core/java/com/android/server/vibrator"
+ "name": "FrameworksVibratorServicesTests",
+ "options": [
+ {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksVibratorServicesTests",
+ "options": [
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
}
]
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 8bb21b3..867c061 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -66,6 +66,9 @@
assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+ assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+ assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1));
+
assertEquals(originalSegments, segments);
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
new file mode 100644
index 0000000..6630cca
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class SplitSegmentsAdapterTest {
+ private static final int PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX = 10;
+
+ private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+ /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+ private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+ new VibratorInfo.FrequencyProfile(
+ /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
+ /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
+
+ private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
+ private static final VibratorInfo PWLE_VIBRATOR_INFO = createVibratorInfo(
+ IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+
+ private SplitSegmentsAdapter mAdapter;
+
+ @Before
+ public void setUp() throws Exception {
+ mAdapter = new SplitSegmentsAdapter();
+ }
+
+ @Test
+ public void testStepAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+ new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(originalSegments);
+ }
+
+ @Test
+ public void testRampSegments_noPwleCapabilities_returnsOriginalSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+ /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+ new RampSegment(/* startAmplitude= */ 0.2f, /* endAmplitude*/ 0.8f,
+ /* startFrequencyHz= */ 60, /* endFrequencyHz= */ 90, /* duration= */ 10)));
+ List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+ .isEqualTo(-1);
+ assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+ .isEqualTo(1);
+
+ assertThat(segments).isEqualTo(originalSegments);
+ }
+
+ @Test
+ public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() {
+ List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+ /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+ new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+ /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+ /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
+ List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+ /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+ new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
+ /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
+ new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
+ /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
+ /* duration= */ 8),
+ new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
+ /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
+ new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+ new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+ /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
+
+ VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
+ .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+ .setPwlePrimitiveDurationMax(10)
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .build();
+
+ // Update repeat index to skip the ramp splits.
+ assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 3))
+ .isEqualTo(5);
+ assertThat(segments).isEqualTo(expectedSegments);
+ }
+
+ private static VibratorInfo createVibratorInfo(int... capabilities) {
+ return new VibratorInfo.Builder(0)
+ .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+ .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+ .setPwlePrimitiveDurationMax(PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX)
+ .build();
+ }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index 58deeec..82deff0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -66,43 +66,13 @@
assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
+ assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+ assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1));
+
assertEquals(originalSegments, segments);
}
@Test
- public void testRampSegments_withPwleDurationLimit_splitsLongRamps() {
- List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
- new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
- /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
- new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
- /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
- new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
- /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
- List<VibrationEffectSegment> expectedSegments = Arrays.asList(
- new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
- /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
- new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
- /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
- new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
- /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
- /* duration= */ 8),
- new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
- /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
- new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
- /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
-
- VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
- .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
- .setPwlePrimitiveDurationMax(10)
- .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
- .build();
-
- // Update repeat index to skip the ramp splits.
- assertEquals(4, mAdapter.adaptToVibrator(vibratorInfo, segments, 2));
- assertEquals(expectedSegments, segments);
- }
-
- @Test
public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 709e9c3..edc5df2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -60,6 +60,7 @@
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.LargeTest;
import android.util.SparseArray;
@@ -1004,6 +1005,7 @@
mVibratorProviders.get(3).getEffectSegments(vibrationId));
}
+ @FlakyTest
@Test
public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
int[] vibratorIds = new int[]{1, 2};
@@ -1020,9 +1022,12 @@
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
.compose();
CombinedVibration effect = CombinedVibration.createParallel(composed);
- long vibrationId = startThreadAndDispatcher(effect);
-
+ // We create the HalVibration here to obtain the vibration id and use it to mock the
+ // required response when calling triggerSyncedVibration.
+ HalVibration halVibration = createVibration(effect);
+ long vibrationId = halVibration.id;
when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+ startThreadAndDispatcher(halVibration);
assertTrue(waitUntil(
() -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
@@ -1054,7 +1059,6 @@
mVibratorProviders.get(4).setSupportedPrimitives(
VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
- when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
VibrationEffect composed = VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
@@ -1065,7 +1069,12 @@
.addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
.addVibrator(4, composed)
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ // We create the HalVibration here to obtain the vibration id and use it to mock the
+ // required response when calling triggerSyncedVibration.
+ HalVibration halVibration = createVibration(effect);
+ long vibrationId = halVibration.id;
+ when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+ startThreadAndDispatcher(halVibration);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
@@ -1115,13 +1124,17 @@
mockVibrators(vibratorIds);
mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
- when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
CombinedVibration effect = CombinedVibration.startParallel()
.addVibrator(1, VibrationEffect.createOneShot(10, 100))
.addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
.combine();
- long vibrationId = startThreadAndDispatcher(effect);
+ // We create the HalVibration here to obtain the vibration id and use it to mock the
+ // required response when calling triggerSyncedVibration.
+ HalVibration halVibration = createVibration(effect);
+ long vibrationId = halVibration.id;
+ when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false);
+ startThreadAndDispatcher(halVibration);
waitForCompletion();
long expectedCap = IVibratorManager.CAP_SYNC
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 9067593..42e3383 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -65,6 +65,7 @@
<activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInVirtualDisplay"
android:resizeableActivity="true" />
<activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity"
+ android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation"
android:screenOrientation="sensorLandscape"
android:showWhenLocked="true"
android:turnScreenOn="true" />
diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS
index cece37f..78b867f 100644
--- a/services/tests/wmtests/OWNERS
+++ b/services/tests/wmtests/OWNERS
@@ -3,3 +3,5 @@
# Voice Interaction
per-file *Assist* = file:/core/java/android/service/voice/OWNERS
+
+natanieljr@google.com
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 62875e5..896edff 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -27,6 +27,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,6 +42,11 @@
public class CombinationKeyTests extends ShortcutKeyTestBase {
private static final long A11Y_KEY_HOLD_MILLIS = 3500;
+ @Before
+ public void setUp() {
+ setUpPhoneWindowManager();
+ }
+
/**
* Power-VolDown to take screenshot.
*/
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 9029bc4..2c35cf0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -44,6 +44,7 @@
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
@Presubmit
@@ -61,6 +62,11 @@
META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
}
+ @Before
+ public void setUp() {
+ setUpPhoneWindowManager();
+ }
+
/**
* Test meta+ shortcuts defined in bookmarks.xml.
*/
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index c3b7849..6f65406 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -26,6 +26,7 @@
import android.provider.Settings;
import android.view.Display;
+import org.junit.Before;
import org.junit.Test;
/**
@@ -35,6 +36,11 @@
* atest WmTests:PowerKeyGestureTests
*/
public class PowerKeyGestureTests extends ShortcutKeyTestBase {
+ @Before
+ public void setUp() {
+ setUpPhoneWindowManager();
+ }
+
/**
* Power single press to turn screen on/off.
*/
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index bf88ce4..f83aecb 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -39,6 +39,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
@@ -53,12 +54,17 @@
import android.view.KeyEvent;
import android.view.ViewConfiguration;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+
import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
import java.util.Map;
class ShortcutKeyTestBase {
+ @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
TestPhoneWindowManager mPhoneWindowManager;
final Context mContext = spy(getInstrumentation().getTargetContext());
@@ -78,18 +84,35 @@
MODIFIER = unmodifiableMap(map);
}
- @Before
- public void setUp() {
+ /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */
+ protected final void setUpPhoneWindowManager() {
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ false);
+ }
+
+ /**
+ * Creates and sets up a {@link TestPhoneWindowManager} instance.
+ *
+ * <p>Subclasses must call this at the start of the test if they intend to interact with phone
+ * window manager.
+ *
+ * @param supportSettingsUpdate {@code true} if this test should read and listen to provider
+ * settings values.
+ */
+ protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
if (Looper.myLooper() == null) {
Looper.prepare();
}
- mPhoneWindowManager = new TestPhoneWindowManager(mContext);
+ doReturn(mSettingsProviderRule.mockContentResolver(mContext))
+ .when(mContext).getContentResolver();
+ mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
}
@After
public void tearDown() {
- mPhoneWindowManager.tearDown();
+ if (mPhoneWindowManager != null) {
+ mPhoneWindowManager.tearDown();
+ }
}
void sendKeyCombination(int[] keyCodes, long duration) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index feca326..d6c821f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -19,7 +19,8 @@
import android.platform.test.annotations.Presubmit;
import android.view.KeyEvent;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
import com.android.internal.annotations.Keep;
import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
@@ -32,7 +33,7 @@
import junitparams.Parameters;
@Presubmit
-@SmallTest
+@LargeTest
@RunWith(JUnitParamsRunner.class)
public class ShortcutLoggingTests extends ShortcutKeyTestBase {
@@ -221,9 +222,8 @@
}
@Before
- @Override
public void setUp() {
- super.setUp();
+ setUpPhoneWindowManager();
mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
mPhoneWindowManager.overrideLaunchHome();
mPhoneWindowManager.overrideSearchKeyBehavior(
@@ -235,6 +235,7 @@
}
@Test
+ @FlakyTest(bugId = 293273386)
@Parameters(method = "shortcutTestArguments")
public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
int expectedKey, int expectedModifierState) {
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 fe8017e..c433e64 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -16,21 +16,14 @@
package com.android.server.policy;
+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.doAnswer;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-
-import android.content.Context;
-import android.content.res.Resources;
+import android.provider.Settings;
import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
/**
* Test class for stem key gesture.
@@ -39,17 +32,13 @@
* atest WmTests:StemKeyGestureTests
*/
public class StemKeyGestureTests extends ShortcutKeyTestBase {
- @Mock private Resources mResources;
-
/**
* Stem single key should not launch behavior during set up.
*/
@Test
public void stemSingleKey_duringSetup_doNothing() {
- stemKeySetup(
- () -> overrideBehavior(
- com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
- SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(false);
@@ -63,10 +52,9 @@
*/
@Test
public void stemSingleKey_AfterSetup_openAllApp() {
- stemKeySetup(
- () -> overrideBehavior(
- com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
- SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+ overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+ setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+ mPhoneWindowManager.overrideStartActivity();
mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -75,28 +63,7 @@
mPhoneWindowManager.assertOpenAllAppView();
}
- private void stemKeySetup(Runnable behaviorOverrideRunnable) {
- super.tearDown();
- setupResourcesMock();
- behaviorOverrideRunnable.run();
- super.setUp();
- }
-
- private void setupResourcesMock() {
- Resources realResources = mContext.getResources();
-
- mResources = Mockito.mock(Resources.class);
- doReturn(mResources).when(mContext).getResources();
-
- doAnswer(invocation -> realResources.getXml((Integer) invocation.getArguments()[0]))
- .when(mResources).getXml(anyInt());
- doAnswer(invocation -> realResources.getString((Integer) invocation.getArguments()[0]))
- .when(mResources).getString(anyInt());
- doAnswer(invocation -> realResources.getBoolean((Integer) invocation.getArguments()[0]))
- .when(mResources).getBoolean(anyInt());
- }
-
- private void overrideBehavior(int resId, int expectedBehavior) {
- doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
+ 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 1866767..7e7a9e1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.description;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -177,16 +178,16 @@
}
}
- TestPhoneWindowManager(Context context) {
+ TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
MockitoAnnotations.initMocks(this);
mHandlerThread = new HandlerThread("fake window manager");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mHandler.runWithScissors(this::setUp, 0 /* timeout */);
+ mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */);
}
- private void setUp() {
+ private void setUp(boolean supportSettingsUpdate) {
mPhoneWindowManager = spy(new PhoneWindowManager());
// Use stubOnly() to reduce memory usage if it doesn't need verification.
@@ -266,7 +267,15 @@
});
doNothing().when(mPhoneWindowManager).initializeHdmiState();
- doNothing().when(mPhoneWindowManager).updateSettings();
+ if (supportSettingsUpdate) {
+ doAnswer(inv -> {
+ // Make any call to updateSettings run synchronously for tests.
+ mPhoneWindowManager.updateSettings(null);
+ return null;
+ }).when(mPhoneWindowManager).updateSettings(any(Handler.class));
+ } else {
+ doNothing().when(mPhoneWindowManager).updateSettings(any());
+ }
doNothing().when(mPhoneWindowManager).screenTurningOn(anyInt(), any());
doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt());
doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt(), anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
index bd6ac58..71d40de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
@@ -34,6 +34,8 @@
import android.os.strictmode.InstanceCountViolation;
import android.util.Log;
+import com.android.server.wm.utils.CommonUtils;
+
import org.junit.After;
import org.junit.Test;
@@ -63,6 +65,10 @@
activity.finish();
}
}
+ if (!mStartedActivityList.isEmpty()) {
+ CommonUtils.waitUntilActivityRemoved(
+ mStartedActivityList.get(mStartedActivityList.size() - 1));
+ }
mStartedActivityList.clear();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index f6f3f03..93adddb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -54,6 +54,8 @@
import androidx.test.filters.MediumTest;
+import com.android.server.wm.utils.CommonUtils;
+
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -172,6 +174,7 @@
instrumentation.removeMonitor(monitor);
if (mainActivity != null) {
mainActivity.finish();
+ CommonUtils.waitUntilActivityRemoved(mainActivity);
}
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 7b4392b..56c3ec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -317,7 +317,8 @@
assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got "
+ mDataRequester.getPendingScreenshotCount(),
numPendingScreenshots, mDataRequester.getPendingScreenshotCount());
- assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted);
+ assertEquals("Expected request NOT completed, unless no pending data",
+ numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted);
mGate.countDown();
waitForIdle(mHandler);
assertEquals("Expected " + numReceivedData + " data, received "
@@ -376,14 +377,7 @@
@Override
public void onAssistRequestCompleted() {
- mHandler.post(() -> {
- try {
- mGate.await(10, TimeUnit.SECONDS);
- mRequestCompleted = true;
- } catch (InterruptedException e) {
- Log.e(TAG, "Failed to wait", e);
- }
- });
+ mRequestCompleted = true;
}
}
}
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 6d7f2c1..1c86758 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -570,6 +570,7 @@
final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext));
final Resources resourcesSpy = Mockito.spy(contextSpy.getResources());
+ spyOn(mAtm.mTaskOrganizerController);
when(contextSpy.getResources()).thenReturn(resourcesSpy);
MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class)
@@ -597,7 +598,8 @@
mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity);
assertTrue(toHomeBuilder.mIsLaunchBehind);
toHomeBuilder.build();
- verify(animationHandler, never()).createStartingSurface(any());
+ verify(mAtm.mTaskOrganizerController, never())
+ .addWindowlessStartingSurface(any(), any(), any(), any(), any());
animationHandler.clearBackAnimateTarget();
// Back to ACTIVITY and TASK have the same logic, just with different target.
@@ -609,9 +611,11 @@
assertFalse(toActivityBuilder.mIsLaunchBehind);
toActivityBuilder.build();
if (preferWindowlessSurface) {
- verify(animationHandler).createStartingSurface(any());
+ verify(mAtm.mTaskOrganizerController)
+ .addWindowlessStartingSurface(any(), any(), any(), any(), any());
} else {
- verify(animationHandler, never()).createStartingSurface(any());
+ verify(mAtm.mTaskOrganizerController, never())
+ .addWindowlessStartingSurface(any(), any(), any(), any(), any());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index bbec091..41c0caae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -453,6 +453,78 @@
displayAreaBounds.width(), displayAreaBounds.height());
}
+ @Test
+ public void testDisplayContentUpdatesRecording_withoutSurface() {
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ setUpDefaultTaskDisplayAreaWindowToken();
+
+ // WHEN getting the DisplayContent for the new virtual display without providing a valid
+ // size from getDisplaySurfaceDefaultSize, i.e. the case of null surface.
+ final DisplayContent virtualDisplay =
+ mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+ doReturn(null).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(anyInt());
+ mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
+ virtualDisplay.updateRecording();
+
+ // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
+ assertThat(virtualDisplay.isCurrentlyRecording()).isFalse();
+ }
+
+ @Test
+ public void testDisplayContentUpdatesRecording_withSurface() {
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ setUpDefaultTaskDisplayAreaWindowToken();
+
+ // WHEN getting the DisplayContent for the virtual display with a valid size from
+ // getDisplaySurfaceDefaultSize (done by surfaceControlMirrors in setUp).
+ final DisplayContent virtualDisplay =
+ mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+ mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
+ virtualDisplay.updateRecording();
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
+ }
+
+ @Test
+ public void testDisplayContentUpdatesRecording_displayMirroring() {
+ // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+ // mirror.
+ setUpDefaultTaskDisplayAreaWindowToken();
+
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ surfaceControlMirrors(sSurfaceSize);
+ // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
+ // call in the test. We need to spy on the DC before updateRecording is called or we can't
+ // verify setDisplayMirroring is called
+ doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
+ // WHEN getting the DisplayContent for the new virtual display.
+ final DisplayContent virtualDisplay =
+ mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+ // Return the default display as the value to mirror to ensure the VD with flag mirroring
+ // creates a ContentRecordingSession automatically.
+ doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ virtualDisplay.updateRecording();
+
+ // THEN mirroring is initiated for the default display's DisplayArea.
+ verify(virtualDisplay).setDisplayMirroring();
+ assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
+ }
+
+ /**
+ * Creates a WindowToken associated with the default task DisplayArea, in order for that
+ * DisplayArea to be mirrored.
+ */
+ private void setUpDefaultTaskDisplayAreaWindowToken() {
+ // GIVEN the default task display area is represented by the WindowToken.
+ spyOn(mWm.mWindowContextListenerController);
+ doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
+ mWm.mWindowContextListenerController).getContainer(any());
+ }
+
/**
* Creates a {@link android.window.WindowContainerToken} associated with a task, in order for
* that task to be recorded.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index bdd178b..d015ca3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -27,12 +27,10 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_PRIVATE;
-import static android.view.Display.INVALID_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.Surface.ROTATION_0;
@@ -112,11 +110,9 @@
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Insets;
-import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.HardwareBuffer;
-import android.hardware.display.VirtualDisplay;
import android.metrics.LogMaker;
import android.os.Binder;
import android.os.RemoteException;
@@ -124,7 +120,6 @@
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.util.DisplayMetrics;
-import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -2655,138 +2650,6 @@
}
@Test
- public void testVirtualDisplayContent_withoutSurface() {
- // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
- // mirror.
- setUpDefaultTaskDisplayAreaWindowToken();
-
- // GIVEN SurfaceControl does not mirror a null surface.
- Point surfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-
- // GIVEN a new VirtualDisplay with an associated surface.
- final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
- final int displayId = display.getDisplay().getDisplayId();
- mWm.mRoot.onDisplayAdded(displayId);
-
- // WHEN getting the DisplayContent for the new virtual display.
- DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
- ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
- DEFAULT_DISPLAY);
- session.setVirtualDisplayId(displayId);
- mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
- actualDC.updateRecording();
-
- // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
- assertThat(actualDC.isCurrentlyRecording()).isFalse();
-
- display.release();
- }
-
- @Test
- public void testVirtualDisplayContent_withSurface() {
- // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
- // mirror.
- setUpDefaultTaskDisplayAreaWindowToken();
-
- // GIVEN SurfaceControl can successfully mirror the provided surface.
- Point surfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- surfaceControlMirrors(surfaceSize);
-
- // GIVEN a new VirtualDisplay with an associated surface.
- final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
- final int displayId = display.getDisplay().getDisplayId();
-
- // GIVEN a session for this display.
- ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
- DEFAULT_DISPLAY);
- session.setVirtualDisplayId(displayId);
- mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
- mWm.mRoot.onDisplayAdded(displayId);
-
- // WHEN getting the DisplayContent for the new virtual display.
- DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
- actualDC.updateRecording();
-
- // THEN mirroring is initiated for the default display's DisplayArea.
- assertThat(actualDC.isCurrentlyRecording()).isTrue();
-
- display.release();
- }
-
- @Test
- public void testVirtualDisplayContent_displayMirroring() {
- // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
- // mirror.
- setUpDefaultTaskDisplayAreaWindowToken();
-
- // GIVEN SurfaceControl can successfully mirror the provided surface.
- Point surfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- surfaceControlMirrors(surfaceSize);
- // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
- // call in the test. We need to spy on the DC before updateRecording is called or we can't
- // verify setDisplayMirroring is called
- doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
-
- // GIVEN a new VirtualDisplay with an associated surface.
- final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
- final int displayId = display.getDisplay().getDisplayId();
-
- // GIVEN a session for this display.
- mWm.mRoot.onDisplayAdded(displayId);
-
- // WHEN getting the DisplayContent for the new virtual display.
- DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
- spyOn(actualDC);
- // Return the default display as the value to mirror to ensure the VD with flag mirroring
- // creates a ContentRecordingSession automatically.
- doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
- actualDC.updateRecording();
-
- // THEN mirroring is initiated for the default display's DisplayArea.
- verify(actualDC).setDisplayMirroring();
- assertThat(actualDC.isCurrentlyRecording()).isTrue();
- display.release();
- }
-
- /**
- * Creates a WindowToken associated with the default task DisplayArea, in order for that
- * DisplayArea to be mirrored.
- */
- private void setUpDefaultTaskDisplayAreaWindowToken() {
- // GIVEN the default task display area is represented by the WindowToken.
- spyOn(mWm.mWindowContextListenerController);
- doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
- mWm.mWindowContextListenerController).getContainer(any());
- }
-
- /**
- * SurfaceControl successfully creates a mirrored surface of the given size.
- */
- private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
- // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
- SurfaceControl mirroredSurface = new SurfaceControl.Builder()
- .setName("mirroredSurface")
- .setBufferSize(surfaceSize.x, surfaceSize.y)
- .setCallsite("mirrorSurface")
- .build();
- doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
- doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
- anyInt());
- return mirroredSurface;
- }
-
- private VirtualDisplay createVirtualDisplay(Point size, Surface surface) {
- return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y,
- DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
- }
-
- @Test
public void testKeepClearAreasMultipleWindows() {
final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
final Rect rect1 = new Rect(0, 0, 10, 10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 1180ebd..c3db241 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -47,6 +47,7 @@
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.window.WindowContextInfo;
import android.window.WindowTokenClient;
import com.android.server.inputmethod.InputMethodDialogWindowContext;
@@ -99,7 +100,7 @@
final WindowProcessController wpc = mAtm.getProcessController(appThread);
mWm.mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
dc.getImeContainer(), TYPE_INPUT_METHOD_DIALOG, null /* options */);
- return dc.getImeContainer().getConfiguration();
+ return new WindowContextInfo(dc.getImeContainer().getConfiguration(), displayId);
}).when(mIWindowManager).attachWindowContextToDisplayArea(any(), any(),
eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());
mDisplayManagerGlobal = DisplayManagerGlobal.getInstance();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index fb95748..0fcae92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -56,6 +56,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -90,6 +93,11 @@
mInstrumentation.waitForIdleSync();
}
+ @After
+ public void tearDown() {
+ CommonUtils.waitUntilActivityRemoved(mActivity);
+ }
+
@Test
public void testScreenshotSecureLayers() throws InterruptedException {
SurfaceControl secureSC = new SurfaceControl.Builder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index 1a4b94b..ce1a46b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -52,6 +52,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import com.android.server.wm.utils.CommonUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +89,7 @@
@After
public void tearDown() {
mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ CommonUtils.waitUntilActivityRemoved(mActivity);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
index abaa776..77290e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
@@ -47,6 +47,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -79,6 +82,12 @@
mHandler = mHandlerThread.getThreadHandler();
}
+ @After
+ public void tearDown() {
+ mHandlerThread.quitSafely();
+ CommonUtils.waitUntilActivityRemoved(mActivity);
+ }
+
@Test
public void testOverlappingSyncsEnsureOrder_WhenTimeout() throws InterruptedException {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index f958e6f..c59a04b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -25,6 +25,9 @@
import androidx.test.rule.ActivityTestRule;
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -54,6 +57,11 @@
}
}
+ @After
+ public void tearDown() {
+ CommonUtils.waitUntilActivityRemoved(mCapturedActivity);
+ }
+
@Test
public void testSurfaceViewSyncDuringResize() throws Throwable {
mCapturedActivity.verifyTest(new SurfaceViewSyncValidatorTestCase(), mName);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 8bc4ced..db08eab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -34,6 +34,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.InputChannel;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import org.junit.Before;
@@ -72,6 +73,7 @@
doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
}
+ @FlakyTest(bugId = 291067614)
@Test
public void testStartAndFinishPositioning() {
assertFalse(mTarget.isPositioningLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index e16208b..16c38ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -60,6 +60,7 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
@@ -76,6 +77,7 @@
private ITaskStackListener mTaskStackListener;
private VirtualDisplay mVirtualDisplay;
private ImageReader mImageReader;
+ private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
private static final int WAIT_TIMEOUT_MS = 5000;
private static final Object sLock = new Object();
@@ -94,6 +96,19 @@
mVirtualDisplay.release();
mImageReader.close();
}
+ // Finish from bottom to top.
+ final int size = mStartedActivities.size();
+ for (int i = 0; i < size; i++) {
+ final Activity activity = mStartedActivities.get(i);
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
+ }
+ // Wait for the last launched activity to be removed.
+ if (size > 0) {
+ CommonUtils.waitUntilActivityRemoved(mStartedActivities.get(size - 1));
+ }
+ mStartedActivities.clear();
}
private VirtualDisplay createVirtualDisplay() {
@@ -381,6 +396,7 @@
throw new RuntimeException("Timed out waiting for Activity");
}
activity.waitForResumeStateChange(true);
+ mStartedActivities.add(activity);
return activity;
}
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 d502cd1..55fda05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -21,9 +21,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
@@ -63,6 +60,8 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -71,9 +70,7 @@
import android.app.ActivityThread;
import android.app.IApplicationThread;
import android.content.pm.ActivityInfo;
-import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.display.VirtualDisplay;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -82,7 +79,6 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
import android.view.IWindow;
@@ -90,7 +86,6 @@
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInsets;
@@ -473,7 +468,7 @@
mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), windowToken.token);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(
- any(), any(), any(), anyInt(), any());
+ any(), any(), any(), anyInt(), any(), anyBoolean());
}
@Test
@@ -489,9 +484,9 @@
final IBinder clientToken = new Binder();
mWm.attachWindowContextToWindowToken(mAppThread, clientToken, windowToken.token);
final WindowProcessController wpc = mAtm.getProcessController(mAppThread);
- verify(mWm.mWindowContextListenerController).registerWindowContainerListener(eq(wpc),
- eq(clientToken), eq(windowToken), eq(TYPE_INPUT_METHOD),
- eq(windowToken.mOptions));
+ verify(mWm.mWindowContextListenerController).registerWindowContainerListener(wpc,
+ clientToken, windowToken, TYPE_INPUT_METHOD, windowToken.mOptions,
+ false /* shouldDispatchConfigWhenRegistering */);
}
@Test
@@ -519,7 +514,7 @@
new InsetsSourceControl.Array(), new Rect(), new float[1]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
- any(), any(), anyInt(), any());
+ any(), any(), anyInt(), any(), anyBoolean());
}
@Test
@@ -575,13 +570,14 @@
mWm.mPerDisplayFocusEnabled = false;
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
- final VirtualDisplay virtualDisplayOwnTouchMode =
- createVirtualDisplay(/* ownFocus= */ true);
+ final DisplayContent display = createMockSimulatedDisplay();
+ display.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
+ final DisplayContent displayOwnTouchMode = createMockSimulatedDisplay();
+ displayOwnTouchMode.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
final int numberOfDisplays = mWm.mRoot.mChildren.size();
assertThat(numberOfDisplays).isAtLeast(3);
final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
- .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+ .filter(d -> (d.getDisplayInfo().flags & FLAG_OWN_FOCUS) == 0)
.count();
assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
@@ -601,12 +597,13 @@
}
@Test
- public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
+ public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
// Enable global touch mode
mWm.mPerDisplayFocusEnabled = true;
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+ final DisplayContent virtualDisplay = createMockSimulatedDisplay();
+ virtualDisplay.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
final int numberOfDisplays = mWm.mRoot.mChildren.size();
assertThat(numberOfDisplays).isAtLeast(2);
@@ -618,99 +615,64 @@
when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
- mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+ mWm.setInTouchMode(!currentTouchMode, virtualDisplay.mDisplayId);
// Ensure that new display touch mode state has changed.
verify(mWm.mInputManager).setInTouchMode(
!currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
- virtualDisplay.getDisplay().getDisplayId());
- }
+ virtualDisplay.mDisplayId);
- @Test
- public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
- // Disable global touch mode
+ // Disable global touch mode and make the virtual display own focus.
mWm.mPerDisplayFocusEnabled = false;
-
- // Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
- final int numberOfDisplays = mWm.mRoot.mChildren.size();
- assertThat(numberOfDisplays).isAtLeast(2);
-
- // Get current touch mode state and setup WMS to run setInTouchMode
- boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
- int callingPid = Binder.getCallingPid();
- int callingUid = Binder.getCallingUid();
- doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
- when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
- android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
-
- mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+ virtualDisplay.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
+ clearInvocations(mWm.mInputManager);
+ mWm.setInTouchMode(!currentTouchMode, virtualDisplay.mDisplayId);
// Ensure that new display touch mode state has changed.
verify(mWm.mInputManager).setInTouchMode(
!currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
- virtualDisplay.getDisplay().getDisplayId());
+ virtualDisplay.mDisplayId);
}
@Test
- public void testSetInTouchModeOnAllDisplays_perDisplayFocusDisabled() {
- testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ false);
- }
-
- @Test
- public void testSetInTouchModeOnAllDisplays_perDisplayFocusEnabled() {
- testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ true);
- }
-
- private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
- // Set global touch mode with the value passed as argument.
- mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
-
+ public void testSetInTouchModeOnAllDisplays() {
// Create a couple of extra displays.
// setInTouchModeOnAllDisplays should ignore the ownFocus setting.
- final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
- final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
+ final DisplayContent display = createMockSimulatedDisplay();
+ display.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
+ final DisplayContent displayOwnTouchMode = createMockSimulatedDisplay();
+ displayOwnTouchMode.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
int callingPid = Binder.getCallingPid();
int callingUid = Binder.getCallingUid();
+ doReturn(true).when(mWm.mInputManager).setInTouchMode(anyBoolean(), anyInt(),
+ anyInt(), anyBoolean(), anyInt());
doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
- for (boolean inTouchMode : new boolean[]{true, false}) {
- mWm.setInTouchModeOnAllDisplays(inTouchMode);
- for (int i = 0; i < mWm.mRoot.mChildren.size(); ++i) {
- DisplayContent dc = mWm.mRoot.mChildren.get(i);
- // All displays that are not already in the desired touch mode are requested to
- // change their touch mode.
- if (dc.isInTouchMode() != inTouchMode) {
- verify(mWm.mInputManager).setInTouchMode(
- true, callingPid, callingUid, /* hasPermission= */ true,
- dc.getDisplay().getDisplayId());
+ final Runnable verification = () -> {
+ for (boolean inTouchMode : new boolean[] { true, false }) {
+ mWm.setInTouchModeOnAllDisplays(inTouchMode);
+ for (int i = 0; i < mRootWindowContainer.getChildCount(); ++i) {
+ final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+ // All displays that are not already in the desired touch mode are requested to
+ // change their touch mode.
+ if (dc.isInTouchMode() != inTouchMode) {
+ verify(mWm.mInputManager, description("perDisplayFocusEnabled="
+ + mWm.mPerDisplayFocusEnabled)).setInTouchMode(true,
+ callingPid, callingUid, /* hasPermission= */ true, dc.mDisplayId);
+ }
}
}
- }
- }
+ };
- private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
- // Create virtual display
- Point surfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
- if (ownFocus) {
- flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
- }
- VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
- surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
- final int displayId = virtualDisplay.getDisplay().getDisplayId();
- mWm.mRoot.onDisplayAdded(displayId);
+ mWm.mPerDisplayFocusEnabled = false;
+ verification.run();
- // Ensure that virtual display was properly created and stored in WRC
- assertThat(mWm.mRoot.getDisplayContent(
- virtualDisplay.getDisplay().getDisplayId())).isNotNull();
-
- return virtualDisplay;
+ clearInvocations(mWm.mInputManager);
+ mWm.mPerDisplayFocusEnabled = true;
+ verification.run();
}
@Test
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 873c7f4..62de67a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -901,6 +901,7 @@
DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.type = Display.TYPE_VIRTUAL;
+ displayInfo.state = Display.STATE_ON;
displayInfo.ownerUid = SYSTEM_UID;
return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY, overrideSettings);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
index 34f9c75..ed23296 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
@@ -18,14 +18,23 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import android.app.Activity;
import android.app.KeyguardManager;
import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.util.Log;
import android.view.KeyEvent;
import androidx.test.uiautomator.UiDevice;
+import java.io.IOException;
+
/** Provides common utility functions. */
public class CommonUtils {
+ private static final String TAG = "CommonUtils";
+ private static final long REMOVAL_TIMEOUT_MS = 3000;
+ private static final long TIMEOUT_INTERVAL_MS = 200;
+
public static UiAutomation getUiAutomation() {
return getInstrumentation().getUiAutomation();
}
@@ -50,4 +59,26 @@
device.pressKeyCode(KeyEvent.KEYCODE_WAKEUP);
device.pressKeyCode(KeyEvent.KEYCODE_MENU);
}
+
+ public static void waitUntilActivityRemoved(Activity activity) {
+ if (!activity.isFinishing()) {
+ activity.finish();
+ }
+ final UiDevice uiDevice = UiDevice.getInstance(getInstrumentation());
+ final String classPattern = activity.getComponentName().flattenToShortString();
+ final long startTime = SystemClock.uptimeMillis();
+ while (SystemClock.uptimeMillis() - startTime <= REMOVAL_TIMEOUT_MS) {
+ SystemClock.sleep(TIMEOUT_INTERVAL_MS);
+ final String windowTokenDump;
+ try {
+ windowTokenDump = uiDevice.executeShellCommand("dumpsys window tokens");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ if (!windowTokenDump.contains(classPattern)) {
+ return;
+ }
+ }
+ Log.i(TAG, "Removal timeout of " + classPattern);
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 997015f..b3db2de 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -365,7 +365,7 @@
// Validate package name
try {
int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName,
- PackageManager.PackageInfoFlags.of(0));
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ANY_USER));
if (!UserHandle.isSameApp(uid, mOriginatorIdentity.uid)) {
throw new SecurityException("Uid " + mOriginatorIdentity.uid +
" attempted to spoof package name " +
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ed1c41f..314150b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9407,10 +9407,11 @@
"missed_incoming_call_sms_pattern_string_array";
/**
- * Indicate the satellite services supported per provider by a carrier.
- *
- * Key is the PLMN of a satellite provider. Value should be an integer array of supported
- * services with the following value:
+ * A PersistableBundle that contains a list of key-value pairs, where the values are integer
+ * arrays.
+ * <p>
+ * Keys are the PLMNs of satellite providers as strings and values are integer arrays of
+ * supported services with the following value:
* <ul>
* <li>1 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VOICE}</li>
* <li>2 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_DATA}</li>
@@ -9419,19 +9420,35 @@
* <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
* </ul>
* <p>
- * If this carrier config is not present, the overlay config
+ * An example config for two PLMNs "123411" and "123412":
+ * <pre>{@code
+ * <carrier_config>
+ * <pbundle_as_map name="carrier_supported_satellite_services_per_provider_bundle">
+ * <int-array name = "123411" num = "2">
+ * <item value = "3"/>
+ * <item value = "5"/>
+ * </int-array>
+ * <int-array name = "123412" num = "1">
+ * <item value = "3"/>
+ * </int-array>
+ * </pbundle_as_map>
+ * </carrier_config>
+ * }</pre>
+ * <p>
+ * If this carrier config is not present, the device overlay config
* {@code config_satellite_services_supported_by_providers} will be used. If the carrier config
- * is present, the supported satellite services will be identified as follows:
+ * is present, the supported services associated with the PLMNs listed in the carrier config
+ * will override that of the device overlay config. The supported satellite services will be
+ * identified as follows:
* <ul>
- * <li>For the PLMN that exists in both provider supported satellite services and carrier
- * supported satellite services, the supported services will be the intersection of the two
- * sets.</li>
- * <li>For the PLMN that is present in provider supported satellite services but not in carrier
- * supported satellite services, the provider supported satellite services will be used.</li>
- * <li>For the PLMN that is present in carrier supported satellite services but not in provider
- * supported satellite services, the PLMN will be ignored.</li>
+ * <li>For each PLMN that exists only in the carrier provided satellite services, use the
+ * carrier provided services as the supported services.</li>
+ * <li>For each PLMN that is present only in the device provided satellite services, use the
+ * device provided services as the supported services.</li>
+ * <li>For each PLMN that is present in both the carrier provided and device provided satellite
+ * services, use the carrier provided services as the supported services.</li>
* </ul>
- *
+ * <p>
* This config is empty by default.
*/
public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 063e2c3..e0fb751 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -93,7 +93,7 @@
* Checks that the [ComponentNameMatcher.NAV_BAR] starts the transition invisible, then becomes
* visible during the unlocking animation and remains visible at the end of the transition
*/
- @Presubmit
+ @FlakyTest(bugId = 293581770)
@Test
fun navBarWindowsVisibilityChanges() {
Assume.assumeFalse(flicker.scenario.isTablet)
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index be87766..3d83caf2 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -21,6 +21,7 @@
#include <string>
#include <vector>
+#include "android-base/parseint.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "androidfw/BigBuffer.h"
@@ -229,14 +230,29 @@
static const char* const sMinorVersion = "19";
// The build id of aapt2 binary.
- static std::string sBuildId = android::build::GetBuildNumber();
+ static const std::string sBuildId = [] {
+ std::string buildNumber = android::build::GetBuildNumber();
- if (android::base::StartsWith(sBuildId, "eng.")) {
- time_t now = time(0);
- tm* ltm = localtime(&now);
+ if (android::base::StartsWith(buildNumber, "eng.")) {
+ // android::build::GetBuildNumber() returns something like "eng.user.20230725.214219" where
+ // the latter two parts are "yyyyMMdd.HHmmss" at build time. Use "yyyyMM" in the fingerprint.
+ std::vector<std::string> parts = util::Split(buildNumber, '.');
+ int buildYear;
+ int buildMonth;
+ if (parts.size() < 3 || parts[2].length() < 6 ||
+ !android::base::ParseInt(parts[2].substr(0, 4), &buildYear) ||
+ !android::base::ParseInt(parts[2].substr(4, 2), &buildMonth)) {
+ // Fallback to localtime() if GetBuildNumber() returns an unexpected output.
+ time_t now = time(0);
+ tm* ltm = localtime(&now);
+ buildYear = 1900 + ltm->tm_year;
+ buildMonth = 1 + ltm->tm_mon;
+ }
- sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon);
- }
+ buildNumber = android::base::StringPrintf("eng.%04d%02d", buildYear, buildMonth);
+ }
+ return buildNumber;
+ }();
return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str());
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index feef049..d41c019 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -81,6 +81,19 @@
mCallback = callback;
}
+ @Override
+ public void onServiceConnected() {
+ if (mCallback != null) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onServiceConnected());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
+ @Override
public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) {
if (mCallback != null) {
final long token = Binder.clearCallingIdentity();
@@ -117,6 +130,7 @@
}
}
+ @Override
public void onHotspotNetworkConnectionStatusChanged(
@NonNull HotspotNetworkConnectionStatus status) {
if (mCallback != null) {
@@ -251,7 +265,6 @@
synchronized (mProxyDataLock) {
mProxyMap.put(callback, proxy);
}
- callback.onServiceConnected();
} catch (RemoteException e) {
Log.e(TAG, "Exception in registerCallback", e);
callback.onRegisterCallbackFailed(e);
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
index 737aa6d..521f943 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/ISharedConnectivityCallback.aidl
@@ -31,4 +31,5 @@
oneway void onKnownNetworksUpdated(in List<KnownNetwork> networks);
oneway void onKnownNetworkConnectionStatusChanged(in KnownNetworkConnectionStatus status);
oneway void onSharedConnectivitySettingsChanged(in SharedConnectivitySettingsState state);
+ oneway void onServiceConnected();
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
index 2bbe919..ebda6f1 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java
@@ -276,6 +276,11 @@
private void onRegisterCallback(ISharedConnectivityCallback callback) {
mRemoteCallbackList.register(callback);
+ try {
+ callback.onServiceConnected();
+ } catch (RemoteException e) {
+ if (DEBUG) Log.w(TAG, "Exception in onRegisterCallback", e);
+ }
if (mCountDownLatch != null) {
mCountDownLatch.countDown();
}