diff options
926 files changed, 24200 insertions, 6932 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 44f3d706a0d0..cbb65667364f 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -63,6 +63,7 @@ aconfig_srcjars = [ ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.tracing.flags-aconfig-java{.generated_srcjars}", + ":android.appwidget.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -108,6 +109,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "telephony_flags_c_lib", + aconfig_declarations: "telephony_flags", +} + // Window aconfig_declarations { name: "com.android.window.flags.window-aconfig", @@ -180,6 +186,18 @@ aconfig_declarations { srcs: ["core/java/android/nfc/*.aconfig"], } +cc_aconfig_library { + name: "android_nfc_flags_aconfig_c_lib", + vendor_available: true, + aconfig_declarations: "android.nfc.flags-aconfig", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + "nfc_nci.st21nfc.default", + ], + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + java_aconfig_library { name: "android.nfc.flags-aconfig-java", aconfig_declarations: "android.nfc.flags-aconfig", @@ -208,6 +226,7 @@ java_aconfig_library { name: "android.security.flags-aconfig-java-host", aconfig_declarations: "android.security.flags-aconfig", host_supported: true, + mode: "test", defaults: ["framework-minus-apex-aconfig-java-defaults"], } @@ -237,6 +256,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.os.flags-aconfig-java-host", + aconfig_declarations: "android.os.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // VirtualDeviceManager java_aconfig_library { name: "android.companion.virtual.flags-aconfig-java", @@ -710,3 +736,16 @@ java_aconfig_library { aconfig_declarations: "android.tracing.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// App Widgets +aconfig_declarations { + name: "android.appwidget.flags-aconfig", + package: "android.appwidget.flags", + srcs: ["core/java/android/appwidget/flags.aconfig"], +} + +java_aconfig_library { + name: "android.appwidget.flags-aconfig-java", + aconfig_declarations: "android.appwidget.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 895ef98c5537..a402c57689d6 100644 --- a/Android.bp +++ b/Android.bp @@ -249,6 +249,7 @@ java_library { "android.se.omapi-V1-java", "android.system.suspend.control.internal-java", "devicepolicyprotosnano", + "ImmutabilityAnnotation", "com.android.sysprop.init", "com.android.sysprop.localization", diff --git a/INPUT_OWNERS b/INPUT_OWNERS index e02ba770cdf8..44b2f3805495 100644 --- a/INPUT_OWNERS +++ b/INPUT_OWNERS @@ -5,3 +5,5 @@ michaelwr@google.com prabirmsp@google.com svv@google.com vdevmurari@google.com + +per-file Virtual*=file:/services/companion/java/com/android/server/companion/virtual/OWNERS diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 29a8dfa96a57..a4174ee6ae17 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -1,13 +1,3 @@ -// b/305195721 -android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:7428: lint: Unresolved link/see tag "android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Drawables DevicePolicyResources.Drawables" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyManager.java:8860: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyManager [101] -android/app/admin/DevicePolicyResourcesManager.java:179: lint: Unresolved link/see tag "android.app.admin.DevicePolicyResources.Strings DevicePolicyResources.Strings" in android.app.admin.DevicePolicyResourcesManager [101] - // b/303477132 android/app/appsearch/AppSearchSchema.java:402: lint: Unresolved link/see tag "#getIndexableNestedProperties()" in android.app.appsearch.AppSearchSchema.DocumentPropertyConfig.Builder [101] android/app/appsearch/AppSearchSession.java:55: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.AppSearchSession [101] diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java index 9d8f1f400950..01486c0708ce 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Event.java +++ b/cmds/uinput/src/com/android/commands/uinput/Event.java @@ -16,19 +16,10 @@ package com.android.commands.uinput; -import android.util.JsonReader; -import android.util.JsonToken; -import android.util.Log; import android.util.SparseArray; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.Objects; -import java.util.function.Function; -import java.util.stream.IntStream; import src.com.android.commands.uinput.InputAbsInfo; @@ -40,46 +31,44 @@ public class Event { private static final String TAG = "UinputEvent"; enum Command { - REGISTER("register"), - DELAY("delay"), - INJECT("inject"), - SYNC("sync"); + REGISTER, + DELAY, + INJECT, + SYNC, + } - final String mCommandName; + // Constants representing evdev event types, from include/uapi/linux/input-event-codes.h in the + // kernel. + public static final int EV_KEY = 0x01; + public static final int EV_REL = 0x02; + public static final int EV_ABS = 0x03; + public static final int EV_MSC = 0x04; + public static final int EV_SW = 0x05; + public static final int EV_LED = 0x11; + public static final int EV_SND = 0x12; + public static final int EV_FF = 0x15; + + public enum UinputControlCode { + UI_SET_EVBIT(100), + UI_SET_KEYBIT(101), + UI_SET_RELBIT(102), + UI_SET_ABSBIT(103), + UI_SET_MSCBIT(104), + UI_SET_LEDBIT(105), + UI_SET_SNDBIT(106), + UI_SET_FFBIT(107), + UI_SET_SWBIT(109), + UI_SET_PROPBIT(110); - Command(String command) { - mCommandName = command; - } - } + private final int mValue; - 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; + UinputControlCode(int value) { this.mValue = value; } + + public int getValue() { + return mValue; + } } // These constants come from "include/uapi/linux/input.h" in the kernel @@ -104,9 +93,9 @@ public class Event { private Bus mBus; private int[] mInjections; private SparseArray<int[]> mConfiguration; - private int mDuration; + private int mDurationMillis; private int mFfEffectsMax = 0; - private String mInputport; + private String mInputPort; private SparseArray<InputAbsInfo> mAbsInfo; private String mSyncToken; @@ -138,12 +127,20 @@ public class Event { return mInjections; } + /** + * Returns a {@link SparseArray} describing the event codes that should be registered for the + * device. The keys are uinput ioctl codes (such as those returned from {@link + * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with + * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT}) + * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112 + * ({@code BTN_MIDDLE}). + */ public SparseArray<int[]> getConfiguration() { return mConfiguration; } - public int getDuration() { - return mDuration; + public int getDurationMillis() { + return mDurationMillis; } public int getFfEffectsMax() { @@ -155,7 +152,7 @@ public class Event { } public String getPort() { - return mInputport; + return mInputPort; } public String getSyncToken() { @@ -174,13 +171,13 @@ public class Event { + ", bus=" + mBus + ", events=" + Arrays.toString(mInjections) + ", configuration=" + mConfiguration - + ", duration=" + mDuration + + ", duration=" + mDurationMillis + "ms" + ", ff_effects_max=" + mFfEffectsMax - + ", port=" + mInputport + + ", port=" + mInputPort + "}"; } - private static class Builder { + public static class Builder { private Event mEvent; Builder() { @@ -191,15 +188,8 @@ public class Event { mEvent.mId = id; } - private void setCommand(String command) { - Objects.requireNonNull(command, "Command must not be null"); - for (Command cmd : Command.values()) { - if (cmd.mCommandName.equals(command)) { - mEvent.mCommand = cmd; - return; - } - } - throw new IllegalStateException("Unrecognized command: " + command); + public void setCommand(String command) { + mEvent.mCommand = Command.valueOf(command.toUpperCase()); } public void setName(String name) { @@ -210,6 +200,12 @@ public class Event { mEvent.mInjections = events; } + /** + * Sets the event codes that should be registered with a {@code register} command. + * + * @param configuration An array of ioctls and event codes, as described at + * {@link Event#getConfiguration()}. + */ public void setConfiguration(SparseArray<int[]> configuration) { mEvent.mConfiguration = configuration; } @@ -226,8 +222,8 @@ public class Event { mEvent.mBus = bus; } - public void setDuration(int duration) { - mEvent.mDuration = duration; + public void setDurationMillis(int durationMillis) { + mEvent.mDurationMillis = durationMillis; } public void setFfEffectsMax(int ffEffectsMax) { @@ -238,8 +234,8 @@ public class Event { mEvent.mAbsInfo = absInfo; } - public void setInputport(String port) { - mEvent.mInputport = port; + public void setInputPort(String port) { + mEvent.mInputPort = port; } public void setSyncToken(String syncToken) { @@ -260,7 +256,7 @@ public class Event { } } case DELAY -> { - if (mEvent.mDuration <= 0) { + if (mEvent.mDurationMillis <= 0) { throw new IllegalStateException("Delay has missing or invalid duration"); } } @@ -278,343 +274,4 @@ public class Event { return mEvent; } } - - /** - * A class that parses the JSON event format from an input stream to build device events. - */ - public static class Reader { - private JsonReader mReader; - - public Reader(InputStreamReader in) { - mReader = new JsonReader(in); - mReader.setLenient(true); - } - - /** - * Get next event entry from JSON file reader. - */ - public Event getNextEvent() throws IOException { - Event e = null; - while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) { - Event.Builder eb = new Event.Builder(); - try { - mReader.beginObject(); - while (mReader.hasNext()) { - String name = mReader.nextName(); - switch (name) { - case "id": - eb.setId(readInt()); - break; - case "command": - eb.setCommand(mReader.nextString()); - break; - case "name": - eb.setName(mReader.nextString()); - break; - case "vid": - eb.setVid(readInt()); - break; - case "pid": - eb.setPid(readInt()); - break; - case "bus": - eb.setBus(readBus()); - break; - case "events": - int[] injections = readInjectedEvents().stream() - .mapToInt(Integer::intValue).toArray(); - eb.setInjections(injections); - break; - case "configuration": - eb.setConfiguration(readConfiguration()); - break; - case "ff_effects_max": - eb.setFfEffectsMax(readInt()); - break; - case "abs_info": - eb.setAbsInfo(readAbsInfoArray()); - break; - case "duration": - eb.setDuration(readInt()); - break; - case "port": - eb.setInputport(mReader.nextString()); - break; - case "syncToken": - eb.setSyncToken(mReader.nextString()); - break; - default: - mReader.skipValue(); - } - } - mReader.endObject(); - } catch (IllegalStateException ex) { - error("Error reading in object, ignoring.", ex); - consumeRemainingElements(); - mReader.endObject(); - continue; - } - e = eb.build(); - } - - return e; - } - - private ArrayList<Integer> readInjectedEvents() throws IOException { - ArrayList<Integer> data = new ArrayList<>(); - try { - mReader.beginArray(); - while (mReader.hasNext()) { - // Read events in groups of three, because we expect an event type, event code, - // and event value. - final int type = readEvdevEventType(); - data.add(type); - data.add(readEvdevEventCode(type)); - data.add(readInt()); - } - mReader.endArray(); - } catch (IllegalStateException | NumberFormatException e) { - consumeRemainingElements(); - mReader.endArray(); - throw new IllegalStateException("Encountered malformed data.", e); - } - return data; - } - - private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException { - switch (mReader.peek()) { - case NUMBER: { - return mReader.nextInt(); - } - case STRING: { - final var str = mReader.nextString(); - try { - // Attempt to first parse the value as an int. - return Integer.decode(str); - } catch (NumberFormatException e) { - // Then fall back to the supplied function. - return stringToInt.apply(str); - } - } - default: { - throw new IllegalStateException( - "Encountered malformed data. Expected int or string."); - } - } - } - - private int readInt() throws IOException { - return readValueAsInt((str) -> { - throw new IllegalStateException("Encountered malformed data. Expected int."); - }); - } - - private Bus readBus() throws IOException { - String val = mReader.nextString(); - return Bus.valueOf(val.toUpperCase()); - } - - private SparseArray<int[]> readConfiguration() - throws IllegalStateException, IOException { - SparseArray<int[]> configuration = new SparseArray<>(); - try { - mReader.beginArray(); - while (mReader.hasNext()) { - UinputControlCode controlCode = null; - IntStream data = null; - mReader.beginObject(); - while (mReader.hasNext()) { - String name = mReader.nextName(); - switch (name) { - case "type": - controlCode = readUinputControlCode(); - break; - case "data": - Objects.requireNonNull(controlCode, - "Configuration 'type' must be specified before 'data'."); - data = readDataForControlCode(controlCode) - .stream().mapToInt(Integer::intValue); - break; - default: - consumeRemainingElements(); - mReader.endObject(); - throw new IllegalStateException( - "Invalid key in device configuration: " + name); - } - } - mReader.endObject(); - if (controlCode != null && data != null) { - final int[] existing = configuration.get(controlCode.mValue); - configuration.put(controlCode.mValue, existing == null ? data.toArray() - : IntStream.concat(IntStream.of(existing), data).toArray()); - } - } - mReader.endArray(); - } catch (IllegalStateException | NumberFormatException e) { - consumeRemainingElements(); - mReader.endArray(); - throw new IllegalStateException("Encountered malformed data.", e); - } - return configuration; - } - - private UinputControlCode readUinputControlCode() throws IOException { - var code = readValueAsInt((controlTypeStr) -> { - for (UinputControlCode controlCode : UinputControlCode.values()) { - if (controlCode.mName.equals(controlTypeStr)) { - return controlCode.mValue; - } - } - return -1; - }); - for (UinputControlCode controlCode : UinputControlCode.values()) { - if (controlCode.mValue == code) { - return controlCode; - } - } - return null; - } - - private List<Integer> readDataForControlCode( - UinputControlCode controlCode) throws IOException { - return switch (controlCode) { - case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType); - case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_KEY)); - case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_REL)); - case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_ABS)); - case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_MSC)); - case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_LED)); - case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SND)); - case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_FF)); - case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SW)); - case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp); - }; - } - - interface IntValueReader { - int readNextValue() throws IOException; - } - - private ArrayList<Integer> readArrayAsInts( - IntValueReader nextValueReader) throws IOException { - ArrayList<Integer> data = new ArrayList<>(); - try { - mReader.beginArray(); - while (mReader.hasNext()) { - data.add(nextValueReader.readNextValue()); - } - mReader.endArray(); - } catch (IllegalStateException | NumberFormatException e) { - consumeRemainingElements(); - mReader.endArray(); - throw new IllegalStateException("Encountered malformed data.", e); - } - return data; - } - - private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException { - InputAbsInfo absInfo = new InputAbsInfo(); - try { - mReader.beginObject(); - while (mReader.hasNext()) { - String name = mReader.nextName(); - switch (name) { - case "value": - absInfo.value = readInt(); - break; - case "minimum": - absInfo.minimum = readInt(); - break; - case "maximum": - absInfo.maximum = readInt(); - break; - case "fuzz": - absInfo.fuzz = readInt(); - break; - case "flat": - absInfo.flat = readInt(); - break; - case "resolution": - absInfo.resolution = readInt(); - break; - default: - consumeRemainingElements(); - mReader.endObject(); - throw new IllegalStateException("Invalid key in abs info: " + name); - } - } - mReader.endObject(); - } catch (IllegalStateException | NumberFormatException e) { - consumeRemainingElements(); - mReader.endObject(); - throw new IllegalStateException("Encountered malformed data.", e); - } - return absInfo; - } - - private SparseArray<InputAbsInfo> readAbsInfoArray() - throws IllegalStateException, IOException { - SparseArray<InputAbsInfo> infoArray = new SparseArray<>(); - try { - mReader.beginArray(); - while (mReader.hasNext()) { - int type = 0; - InputAbsInfo absInfo = null; - mReader.beginObject(); - while (mReader.hasNext()) { - String name = mReader.nextName(); - switch (name) { - case "code": - type = readEvdevEventCode(EV_ABS); - break; - case "info": - absInfo = readAbsInfo(); - break; - default: - consumeRemainingElements(); - mReader.endObject(); - throw new IllegalStateException("Invalid key in abs info array: " - + name); - } - } - mReader.endObject(); - if (absInfo != null) { - infoArray.put(type, absInfo); - } - } - mReader.endArray(); - } catch (IllegalStateException | NumberFormatException e) { - consumeRemainingElements(); - mReader.endArray(); - throw new IllegalStateException("Encountered malformed data.", e); - } - return infoArray; - } - - private int readEvdevEventType() throws IOException { - return readValueAsInt(Device::getEvdevEventTypeByLabel); - } - - private int readEvdevEventCode(int type) throws IOException { - return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str)); - } - - private int readEvdevInputProp() throws IOException { - return readValueAsInt(Device::getEvdevInputPropByLabel); - } - - private void consumeRemainingElements() throws IOException { - while (mReader.hasNext()) { - mReader.skipValue(); - } - } - } - - private static void error(String msg, Exception e) { - System.out.println(msg); - Log.e(TAG, msg); - if (e != null) { - Log.e(TAG, Log.getStackTraceString(e)); - } - } } diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java new file mode 100644 index 000000000000..53d0be819dae --- /dev/null +++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java @@ -0,0 +1,333 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.commands.uinput; + +import android.util.JsonReader; +import android.util.JsonToken; +import android.util.Log; +import android.util.SparseArray; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.IntStream; + +import src.com.android.commands.uinput.InputAbsInfo; + +/** + * A class that parses the JSON-like event format described in the README to build {@link Event}s. + */ +public class JsonStyleParser { + private static final String TAG = "UinputJsonStyleParser"; + + private JsonReader mReader; + + public JsonStyleParser(InputStreamReader in) { + mReader = new JsonReader(in); + mReader.setLenient(true); + } + + /** + * Gets the next event entry from the JSON file. + */ + public Event getNextEvent() throws IOException { + Event e = null; + while (e == null && mReader.peek() != JsonToken.END_DOCUMENT) { + Event.Builder eb = new Event.Builder(); + try { + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "id" -> eb.setId(readInt()); + case "command" -> eb.setCommand(mReader.nextString()); + case "name" -> eb.setName(mReader.nextString()); + case "vid" -> eb.setVid(readInt()); + case "pid" -> eb.setPid(readInt()); + case "bus" -> eb.setBus(readBus()); + case "events" -> { + int[] injections = readInjectedEvents().stream() + .mapToInt(Integer::intValue).toArray(); + eb.setInjections(injections); + } + case "configuration" -> eb.setConfiguration(readConfiguration()); + case "ff_effects_max" -> eb.setFfEffectsMax(readInt()); + case "abs_info" -> eb.setAbsInfo(readAbsInfoArray()); + case "duration" -> eb.setDurationMillis(readInt()); + case "port" -> eb.setInputPort(mReader.nextString()); + case "syncToken" -> eb.setSyncToken(mReader.nextString()); + default -> mReader.skipValue(); + } + } + mReader.endObject(); + } catch (IllegalStateException ex) { + error("Error reading in object, ignoring.", ex); + consumeRemainingElements(); + mReader.endObject(); + continue; + } + e = eb.build(); + } + + return e; + } + + private ArrayList<Integer> readInjectedEvents() throws IOException { + ArrayList<Integer> data = new ArrayList<>(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + // Read events in groups of three, because we expect an event type, event code, + // and event value. + final int type = readEvdevEventType(); + data.add(type); + data.add(readEvdevEventCode(type)); + data.add(readInt()); + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return data; + } + + private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException { + switch (mReader.peek()) { + case NUMBER: { + return mReader.nextInt(); + } + case STRING: { + final var str = mReader.nextString(); + try { + // Attempt to first parse the value as an int. + return Integer.decode(str); + } catch (NumberFormatException e) { + // Then fall back to the supplied function. + return stringToInt.apply(str); + } + } + default: { + throw new IllegalStateException( + "Encountered malformed data. Expected int or string."); + } + } + } + + private int readInt() throws IOException { + return readValueAsInt((str) -> { + throw new IllegalStateException("Encountered malformed data. Expected int."); + }); + } + + private Event.Bus readBus() throws IOException { + String val = mReader.nextString(); + return Event.Bus.valueOf(val.toUpperCase()); + } + + private SparseArray<int[]> readConfiguration() + throws IllegalStateException, IOException { + SparseArray<int[]> configuration = new SparseArray<>(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + Event.UinputControlCode controlCode = null; + IntStream data = null; + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "type": + controlCode = readUinputControlCode(); + break; + case "data": + Objects.requireNonNull(controlCode, + "Configuration 'type' must be specified before 'data'."); + data = readDataForControlCode(controlCode) + .stream().mapToInt(Integer::intValue); + break; + default: + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException( + "Invalid key in device configuration: " + name); + } + } + mReader.endObject(); + if (controlCode != null && data != null) { + final int[] existing = configuration.get(controlCode.getValue()); + configuration.put(controlCode.getValue(), existing == null ? data.toArray() + : IntStream.concat(IntStream.of(existing), data).toArray()); + } + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return configuration; + } + + private Event.UinputControlCode readUinputControlCode() throws IOException { + var code = readValueAsInt((controlTypeStr) -> { + try { + return Event.UinputControlCode.valueOf(controlTypeStr).getValue(); + } catch (IllegalArgumentException ex) { + return -1; + } + }); + for (Event.UinputControlCode controlCode : Event.UinputControlCode.values()) { + if (controlCode.getValue() == code) { + return controlCode; + } + } + return null; + } + + private List<Integer> readDataForControlCode( + Event.UinputControlCode controlCode) throws IOException { + return switch (controlCode) { + case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType); + case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_KEY)); + case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_REL)); + case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_ABS)); + case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_MSC)); + case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_LED)); + case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SND)); + case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_FF)); + case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(Event.EV_SW)); + case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp); + }; + } + + interface IntValueReader { + int readNextValue() throws IOException; + } + + private ArrayList<Integer> readArrayAsInts( + IntValueReader nextValueReader) throws IOException { + ArrayList<Integer> data = new ArrayList<>(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + data.add(nextValueReader.readNextValue()); + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return data; + } + + private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException { + InputAbsInfo absInfo = new InputAbsInfo(); + try { + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "value" -> absInfo.value = readInt(); + case "minimum" -> absInfo.minimum = readInt(); + case "maximum" -> absInfo.maximum = readInt(); + case "fuzz" -> absInfo.fuzz = readInt(); + case "flat" -> absInfo.flat = readInt(); + case "resolution" -> absInfo.resolution = readInt(); + default -> { + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException("Invalid key in abs info: " + name); + } + } + } + mReader.endObject(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return absInfo; + } + + private SparseArray<InputAbsInfo> readAbsInfoArray() + throws IllegalStateException, IOException { + SparseArray<InputAbsInfo> infoArray = new SparseArray<>(); + try { + mReader.beginArray(); + while (mReader.hasNext()) { + int type = 0; + InputAbsInfo absInfo = null; + mReader.beginObject(); + while (mReader.hasNext()) { + String name = mReader.nextName(); + switch (name) { + case "code" -> type = readEvdevEventCode(Event.EV_ABS); + case "info" -> absInfo = readAbsInfo(); + default -> { + consumeRemainingElements(); + mReader.endObject(); + throw new IllegalStateException("Invalid key in abs info array: " + + name); + } + } + } + mReader.endObject(); + if (absInfo != null) { + infoArray.put(type, absInfo); + } + } + mReader.endArray(); + } catch (IllegalStateException | NumberFormatException e) { + consumeRemainingElements(); + mReader.endArray(); + throw new IllegalStateException("Encountered malformed data.", e); + } + return infoArray; + } + + private int readEvdevEventType() throws IOException { + return readValueAsInt(Device::getEvdevEventTypeByLabel); + } + + private int readEvdevEventCode(int type) throws IOException { + return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str)); + } + + private int readEvdevInputProp() throws IOException { + return readValueAsInt(Device::getEvdevInputPropByLabel); + } + + private void consumeRemainingElements() throws IOException { + while (mReader.hasNext()) { + mReader.skipValue(); + } + } + + private static void error(String msg, Exception e) { + System.out.println(msg); + Log.e(TAG, msg); + if (e != null) { + Log.e(TAG, Log.getStackTraceString(e)); + } + } +} diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java index 47b7a354f330..fe76abb9861d 100644 --- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java +++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java @@ -35,7 +35,7 @@ import java.util.Objects; public class Uinput { private static final String TAG = "UINPUT"; - private final Event.Reader mReader; + private final JsonStyleParser mParser; private final SparseArray<Device> mDevices; private static void usage() { @@ -74,7 +74,7 @@ public class Uinput { private Uinput(InputStream in) { mDevices = new SparseArray<Device>(); try { - mReader = new Event.Reader(new InputStreamReader(in, "UTF-8")); + mParser = new JsonStyleParser(new InputStreamReader(in, "UTF-8")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } @@ -83,7 +83,7 @@ public class Uinput { private void run() { try { Event e = null; - while ((e = mReader.getNextEvent()) != null) { + while ((e = mParser.getNextEvent()) != null) { process(e); } } catch (IOException ex) { @@ -111,7 +111,7 @@ public class Uinput { case REGISTER -> error("Device id=" + e.getId() + " is already registered. Ignoring event."); case INJECT -> d.injectEvent(e.getInjections()); - case DELAY -> d.addDelay(e.getDuration()); + case DELAY -> d.addDelay(e.getDurationMillis()); case SYNC -> d.syncEvent(e.getSyncToken()); } } diff --git a/core/api/current.txt b/core/api/current.txt index 3f4a34b51e40..4256d51b09e4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5316,21 +5316,23 @@ package android.app { method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); method public long getCreationTime(); + method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects(); method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId(); method public int getInterruptionFilter(); method public String getName(); method public android.content.ComponentName getOwner(); method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription(); method @FlaggedApi("android.app.modes_api") public int getType(); - method public android.service.notification.ZenPolicy getZenPolicy(); + method @Nullable public android.service.notification.ZenPolicy getZenPolicy(); method public boolean isEnabled(); method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed(); method public void setConditionId(android.net.Uri); method public void setConfigurationActivity(@Nullable android.content.ComponentName); + method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method public void setEnabled(boolean); method public void setInterruptionFilter(int); method public void setName(String); - method public void setZenPolicy(android.service.notification.ZenPolicy); + method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR; field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3 @@ -5350,6 +5352,7 @@ package android.app { method @NonNull public android.app.AutomaticZenRule build(); method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri); method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName); + method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects); method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean); method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int); method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int); @@ -7861,6 +7864,7 @@ package android.app.admin { field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity"; field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages"; } @@ -14699,31 +14703,31 @@ package android.database.sqlite { } @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable { - method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException; - method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException; - method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException; - method public void bindInt(int, int) throws android.database.sqlite.SQLiteException; - method public void bindLong(int, long) throws android.database.sqlite.SQLiteException; - method public void bindNull(int) throws android.database.sqlite.SQLiteException; - method public void bindText(int, @NonNull String) throws android.database.sqlite.SQLiteException; + method public void bindBlob(int, @NonNull byte[]); + method public void bindBlob(int, @NonNull byte[], int, int); + method public void bindDouble(int, double); + method public void bindInt(int, int); + method public void bindLong(int, long); + method public void bindNull(int); + method public void bindText(int, @NonNull String); method public void clearBindings(); method public void close(); - method @Nullable public byte[] getColumnBlob(int) throws android.database.sqlite.SQLiteException; - method public double getColumnDouble(int) throws android.database.sqlite.SQLiteException; - method public int getColumnInt(int) throws android.database.sqlite.SQLiteException; - method public int getColumnLength(int) throws android.database.sqlite.SQLiteException; - method public long getColumnLong(int) throws android.database.sqlite.SQLiteException; - method @NonNull public String getColumnName(int) throws android.database.sqlite.SQLiteException; - method @NonNull public String getColumnText(int) throws android.database.sqlite.SQLiteException; - method public int getColumnType(int) throws android.database.sqlite.SQLiteException; + method @Nullable public byte[] getColumnBlob(int); + method public double getColumnDouble(int); + method public int getColumnInt(int); + method public int getColumnLength(int); + method public long getColumnLong(int); + method @NonNull public String getColumnName(int); + method @NonNull public String getColumnText(int); + method public int getColumnType(int); method public int getParameterCount(); method public int getParameterIndex(@NonNull String); method @Nullable public String getParameterName(int); method public int getResultColumnCount(); method public boolean isOpen(); - method public int readColumnBlob(int, @NonNull byte[], int, int, int) throws android.database.sqlite.SQLiteException; + method public int readColumnBlob(int, @NonNull byte[], int, int, int); method public void reset(); - method public boolean step() throws android.database.sqlite.SQLiteException; + method public boolean step(); field public static final int SQLITE_DATA_TYPE_BLOB = 4; // 0x4 field public static final int SQLITE_DATA_TYPE_FLOAT = 2; // 0x2 field public static final int SQLITE_DATA_TYPE_INTEGER = 1; // 0x1 @@ -22804,11 +22808,11 @@ package android.media { method public void clearOnSessionLostStateListener(); method public void close(); method public void closeSession(@NonNull byte[]); - method @android.media.MediaDrm.HdcpLevel public int getConnectedHdcpLevel(); + method public int getConnectedHdcpLevel(); method public android.media.MediaDrm.CryptoSession getCryptoSession(@NonNull byte[], @NonNull String, @NonNull String); method @NonNull public android.media.MediaDrm.KeyRequest getKeyRequest(@NonNull byte[], @Nullable byte[], @Nullable String, int, @Nullable java.util.HashMap<java.lang.String,java.lang.String>) throws android.media.NotProvisionedException; method @NonNull public java.util.List<android.media.MediaDrm.LogMessage> getLogMessages(); - method @android.media.MediaDrm.HdcpLevel public int getMaxHdcpLevel(); + method public int getMaxHdcpLevel(); method public static int getMaxSecurityLevel(); method public int getMaxSessionCount(); method public android.os.PersistableBundle getMetrics(); @@ -22822,13 +22826,13 @@ package android.media { method @Deprecated @NonNull public byte[] getSecureStop(@NonNull byte[]); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStopIds(); method @Deprecated @NonNull public java.util.List<byte[]> getSecureStops(); - method @android.media.MediaDrm.SecurityLevel public int getSecurityLevel(@NonNull byte[]); + method public int getSecurityLevel(@NonNull byte[]); method @NonNull public static java.util.List<java.util.UUID> getSupportedCryptoSchemes(); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID); method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String); - method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public static boolean isCryptoSchemeSupported(@NonNull java.util.UUID, @NonNull String, int); method @NonNull public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; - method @NonNull public byte[] openSession(@android.media.MediaDrm.SecurityLevel int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; + method @NonNull public byte[] openSession(int) throws android.media.NotProvisionedException, android.media.ResourceBusyException; method @Nullable public byte[] provideKeyResponse(@NonNull byte[], @NonNull byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(@NonNull byte[]) throws android.media.DeniedByServerException; method @NonNull public java.util.HashMap<java.lang.String,java.lang.String> queryKeyStatus(@NonNull byte[]); @@ -22840,7 +22844,7 @@ package android.media { method public void removeOfflineLicense(@NonNull byte[]); method @Deprecated public void removeSecureStop(@NonNull byte[]); method public boolean requiresSecureDecoder(@NonNull String); - method public boolean requiresSecureDecoder(@NonNull String, @android.media.MediaDrm.SecurityLevel int); + method public boolean requiresSecureDecoder(@NonNull String, int); method public void restoreKeys(@NonNull byte[], @NonNull byte[]); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener); method public void setOnEventListener(@Nullable android.media.MediaDrm.OnEventListener, @Nullable android.os.Handler); @@ -22929,9 +22933,6 @@ package android.media { field public static final int ERROR_ZERO_SUBSAMPLES = 33; // 0x21 } - @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { - } - public static final class MediaDrm.KeyRequest { method @NonNull public byte[] getData(); method @NonNull public String getDefaultUrl(); @@ -23033,9 +23034,6 @@ package android.media { method @NonNull public String getDefaultUrl(); } - @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { - } - public static final class MediaDrm.SessionException extends java.lang.RuntimeException implements android.media.MediaDrmThrowable { ctor public MediaDrm.SessionException(int, @Nullable String); method @Deprecated public int getErrorCode(); @@ -28698,14 +28696,17 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); @@ -28813,6 +28814,7 @@ package android.nfc.cardemulation { method public boolean removeAidsForService(android.content.ComponentName, String); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); @@ -28832,9 +28834,20 @@ package android.nfc.cardemulation { method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>); method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U' field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } @@ -33137,6 +33150,7 @@ package android.os { public static class PerformanceHintManager.Session implements java.io.Closeable { method public void close(); method public void reportActualWorkDuration(long); + method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public void reportActualWorkDuration(@NonNull android.os.WorkDuration); method @FlaggedApi("android.os.adpf_prefer_power_efficiency") public void setPreferPowerEfficiency(boolean); method public void setThreads(@NonNull int[]); method public void updateTargetWorkDuration(long); @@ -33478,6 +33492,7 @@ package android.os { method public static boolean setCurrentTimeMillis(long); method public static void sleep(long); method public static long uptimeMillis(); + method @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public static long uptimeNanos(); } public class TestLooperManager { @@ -33743,6 +33758,22 @@ package android.os { method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibration, @Nullable android.os.VibrationAttributes); } + @FlaggedApi("android.os.adpf_gpu_report_actual_work_duration") public final class WorkDuration implements android.os.Parcelable { + ctor public WorkDuration(); + ctor public WorkDuration(long, long, long, long); + method public int describeContents(); + method public long getActualCpuDurationNanos(); + method public long getActualGpuDurationNanos(); + method public long getActualTotalDurationNanos(); + method public long getWorkPeriodStartTimestampNanos(); + method public void setActualCpuDurationNanos(long); + method public void setActualGpuDurationNanos(long); + method public void setActualTotalDurationNanos(long); + method public void setWorkPeriodStartTimestampNanos(long); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.WorkDuration> CREATOR; + } + public class WorkSource implements android.os.Parcelable { ctor public WorkSource(); ctor public WorkSource(android.os.WorkSource); @@ -37273,6 +37304,7 @@ package android.provider { } public static final class Telephony.Carriers implements android.provider.BaseColumns { + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String ALWAYS_ON = "always_on"; field public static final String APN = "apn"; field public static final String AUTH_TYPE = "authtype"; field @Deprecated public static final String BEARER = "bearer"; @@ -37286,6 +37318,8 @@ package android.provider { field public static final String MMSPORT = "mmsport"; field public static final String MMSPROXY = "mmsproxy"; field @Deprecated public static final String MNC = "mnc"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V4 = "mtu_v4"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String MTU_V6 = "mtu_v6"; field @Deprecated public static final String MVNO_MATCH_DATA = "mvno_match_data"; field @Deprecated public static final String MVNO_TYPE = "mvno_type"; field public static final String NAME = "name"; @@ -37301,6 +37335,8 @@ package android.provider { field public static final String SUBSCRIPTION_ID = "sub_id"; field public static final String TYPE = "type"; field public static final String USER = "user"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_EDITABLE = "user_editable"; + field @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public static final String USER_VISIBLE = "user_visible"; } public static final class Telephony.Mms implements android.provider.Telephony.BaseMmsColumns { @@ -40697,6 +40733,26 @@ package android.service.notification { field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR; } + @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable { + method public int describeContents(); + method public boolean shouldDimWallpaper(); + method public boolean shouldDisplayGrayscale(); + method public boolean shouldSuppressAmbientDisplay(); + method public boolean shouldUseNightMode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR; + } + + @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder { + ctor public ZenDeviceEffects.Builder(); + ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects); + method @NonNull public android.service.notification.ZenDeviceEffects build(); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean); + method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean); + } + public final class ZenPolicy implements android.os.Parcelable { method public int describeContents(); method public int getPriorityCallSenders(); @@ -43651,6 +43707,7 @@ package android.telephony { field public static final String KEY_CHILD_SA_REKEY_SOFT_TIMER_SEC_INT = "iwlan.child_sa_rekey_soft_timer_sec_int"; field public static final String KEY_CHILD_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_cbc_key_size_int_array"; field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_gcm_key_size_int_array"; field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array"; field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int"; field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int"; @@ -43666,12 +43723,15 @@ package android.telephony { field public static final String KEY_IKE_REMOTE_ID_TYPE_INT = "iwlan.ike_remote_id_type_int"; field public static final String KEY_IKE_SESSION_AES_CBC_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_cbc_key_size_int_array"; field public static final String KEY_IKE_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_ctr_key_size_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = "iwlan.ike_session_encryption_aes_gcm_key_size_int_array"; field public static final String KEY_MAX_RETRIES_INT = "iwlan.max_retries_int"; field public static final String KEY_MCC_MNCS_STRING_ARRAY = "iwlan.mcc_mncs_string_array"; field public static final String KEY_NATT_KEEP_ALIVE_TIMER_SEC_INT = "iwlan.natt_keep_alive_timer_sec_int"; field public static final String KEY_PREFIX = "iwlan."; field public static final String KEY_RETRANSMIT_TIMER_MSEC_INT_ARRAY = "iwlan.retransmit_timer_sec_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_aead_algorithms_int_array"; field public static final String KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_child_session_encryption_algorithms_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.enable_aead_algorithms") public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_aead_algorithms_int_array"; field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; @@ -44695,10 +44755,16 @@ package android.telephony { method public int getLastCauseCode(); method @Nullable public android.net.LinkProperties getLinkProperties(); method public int getNetworkType(); + method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus(); method public int getState(); method public int getTransportType(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR; + field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_FAILURE = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_SUCCESS = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.network_validation") public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; // 0x0 } public final class RadioAccessSpecifier implements android.os.Parcelable { @@ -45817,6 +45883,7 @@ package android.telephony.data { method public int getProxyPort(); method public int getRoamingProtocol(); method public String getUser(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") public boolean isAlwaysOn(); method public boolean isEnabled(); method public boolean isPersistent(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -45856,6 +45923,7 @@ package android.telephony.data { public static class ApnSetting.Builder { ctor public ApnSetting.Builder(); method public android.telephony.data.ApnSetting build(); + method @FlaggedApi("com.android.internal.telephony.flags.apn_setting_field_support_flag") @NonNull public android.telephony.data.ApnSetting.Builder setAlwaysOn(boolean); method @NonNull public android.telephony.data.ApnSetting.Builder setApnName(@Nullable String); method @NonNull public android.telephony.data.ApnSetting.Builder setApnTypeBitmask(int); method @NonNull public android.telephony.data.ApnSetting.Builder setAuthType(int); diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index 1308b1fc578b..449249e02768 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -1,40 +1,4 @@ // Baseline format: 1.0 -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[]): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindBlob(int, byte[], int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindDouble(int, double): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindInt(int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindLong(int, long): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindNull(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#bindText(int, String): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnBlob(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnDouble(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnInt(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLength(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnLong(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnName(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnText(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#getColumnType(int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#readColumnBlob(int, byte[], int, int, int): - Methods must not throw unchecked exceptions -BannedThrow: android.database.sqlite.SQLiteRawStatement#step(): - Methods must not throw unchecked exceptions - - BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED: Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: diff --git a/core/api/removed.txt b/core/api/removed.txt index 5a4be65ef559..989bb7756e4c 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -224,6 +224,12 @@ package android.media { ctor public AudioFormat(); } + @Deprecated @IntDef({android.media.MediaDrm.HDCP_LEVEL_UNKNOWN, android.media.MediaDrm.HDCP_NONE, android.media.MediaDrm.HDCP_V1, android.media.MediaDrm.HDCP_V2, android.media.MediaDrm.HDCP_V2_1, android.media.MediaDrm.HDCP_V2_2, android.media.MediaDrm.HDCP_V2_3, android.media.MediaDrm.HDCP_NO_DIGITAL_OUTPUT}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.HdcpLevel { + } + + @Deprecated @IntDef({android.media.MediaDrm.SECURITY_LEVEL_UNKNOWN, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_CRYPTO, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_DECODE, android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface MediaDrm.SecurityLevel { + } + } package android.media.tv { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index de07fdde6267..a7ea753cb422 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3185,6 +3185,7 @@ package android.companion.virtual { field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2 field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1 field public static final int LAUNCH_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0"; } public static interface VirtualDeviceManager.ActivityListener { @@ -3330,6 +3331,53 @@ package android.companion.virtual.audio { } +package android.companion.virtual.camera { + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCamera implements java.io.Closeable { + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig getConfig(); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public interface VirtualCameraCallback { + method public void onProcessCaptureRequest(int, long, @Nullable android.companion.virtual.camera.VirtualCameraMetadata); + method public void onStreamClosed(int); + method public void onStreamConfigured(int, @NonNull android.view.Surface, @NonNull android.companion.virtual.camera.VirtualCameraStreamConfig); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraConfig implements android.os.Parcelable { + method public int describeContents(); + method @StringRes public int getDisplayNameStringRes(); + method @NonNull public java.util.Set<android.companion.virtual.camera.VirtualCameraStreamConfig> getStreamConfigs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraConfig> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder { + ctor public VirtualCameraConfig.Builder(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(int, int, int); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build(); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setDisplayNameStringRes(@StringRes int); + method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback); + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraMetadata implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraMetadata> CREATOR; + } + + @FlaggedApi("android.companion.virtual.flags.virtual_camera") public final class VirtualCameraStreamConfig implements android.os.Parcelable { + ctor public VirtualCameraStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int); + method public int describeContents(); + method public int getFormat(); + method @IntRange(from=1) public int getHeight(); + method @IntRange(from=1) public int getWidth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.camera.VirtualCameraStreamConfig> CREATOR; + } + +} + package android.companion.virtual.sensor { public final class VirtualSensor implements android.os.Parcelable { @@ -4762,7 +4810,7 @@ package android.hardware.hdmi { method public void onChange(@NonNull String); } - @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult { + @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface HdmiControlManager.ControlCallbackResult { } public static interface HdmiControlManager.HotplugEventListener { @@ -11234,15 +11282,11 @@ package android.provider { field public static final String MAX_CONNECTIONS = "max_conns"; field public static final String MODEM_PERSIST = "modem_cognitive"; field @Deprecated public static final String MTU = "mtu"; - field public static final String MTU_V4 = "mtu_v4"; - field public static final String MTU_V6 = "mtu_v6"; field public static final int NO_APN_SET_ID = 0; // 0x0 field public static final String TIME_LIMIT_FOR_MAX_CONNECTIONS = "max_conns_time"; field public static final int UNEDITED = 0; // 0x0 field public static final int USER_DELETED = 2; // 0x2 - field public static final String USER_EDITABLE = "user_editable"; field public static final int USER_EDITED = 1; // 0x1 - field public static final String USER_VISIBLE = "user_visible"; field public static final String WAIT_TIME_RETRY = "wait_time"; } @@ -14793,6 +14837,7 @@ package android.telephony.data { method @Deprecated public int getMtu(); method public int getMtuV4(); method public int getMtuV6(); + method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public int getNetworkValidationStatus(); method @NonNull public java.util.List<java.net.InetAddress> getPcscfAddresses(); method public int getPduSessionId(); method public int getProtocolType(); @@ -14829,6 +14874,7 @@ package android.telephony.data { method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV6(int); + method @FlaggedApi("com.android.internal.telephony.flags.network_validation") @NonNull public android.telephony.data.DataCallResponse.Builder setNetworkValidationStatus(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setPcscfAddresses(@NonNull java.util.List<java.net.InetAddress>); method @NonNull public android.telephony.data.DataCallResponse.Builder setPduSessionId(@IntRange(from=android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET, to=15) int); method @NonNull public android.telephony.data.DataCallResponse.Builder setProtocolType(int); @@ -14908,6 +14954,7 @@ package android.telephony.data { method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile); method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback); + method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setupDataCall(int, @NonNull android.telephony.data.DataProfile, boolean, boolean, int, @Nullable android.net.LinkProperties, @NonNull android.telephony.data.DataServiceCallback); @@ -14969,6 +15016,7 @@ package android.telephony.data { method public final int getSlotIndex(); method public void reportEmergencyDataNetworkPreferredTransportChanged(int); method public void reportThrottleStatusChanged(@NonNull java.util.List<android.telephony.data.ThrottleStatus>); + method @FlaggedApi("com.android.internal.telephony.flags.network_validation") public void requestNetworkValidation(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method public final void updateQualifiedNetworkTypes(int, @NonNull java.util.List<java.lang.Integer>); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 8b20720418b6..75797edfb218 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -541,7 +541,6 @@ package android.app.admin { field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods"; field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended"; field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled"; - field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; } public class DevicePolicyManager { diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 3f9cc65ba1db..8ad6ea207665 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -632,6 +632,7 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_JOYSTICK, InputDevice.SOURCE_SENSOR }) + @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} /** diff --git a/core/java/android/accessibilityservice/TouchInteractionController.java b/core/java/android/accessibilityservice/TouchInteractionController.java index af00f316de7b..6ec956ee7783 100644 --- a/core/java/android/accessibilityservice/TouchInteractionController.java +++ b/core/java/android/accessibilityservice/TouchInteractionController.java @@ -24,6 +24,8 @@ import android.util.ArrayMap; import android.view.MotionEvent; import android.view.accessibility.AccessibilityInteractionClient; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Executor; @@ -92,6 +94,7 @@ public final class TouchInteractionController { STATE_DRAGGING, STATE_DELEGATING }) + @Retention(RetentionPolicy.SOURCE) private @interface State {} // The maximum number of pointers that can be touching the screen at once. (See MAX_POINTER_ID diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index be433d2ab0f2..ed18d81d7914 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -23,7 +23,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.inMultiWindowMode; import static android.os.Process.myUid; + import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; + import static java.lang.Character.MIN_VALUE; import android.annotation.AnimRes; @@ -1000,6 +1002,7 @@ public class Activity extends ContextThemeWrapper FULLSCREEN_MODE_REQUEST_EXIT, FULLSCREEN_MODE_REQUEST_ENTER }) + @Retention(RetentionPolicy.SOURCE) public @interface FullscreenModeRequest {} /** Request type of {@link #requestFullscreenMode(int, OutcomeReceiver)}, to request exiting the @@ -1016,6 +1019,7 @@ public class Activity extends ContextThemeWrapper OVERRIDE_TRANSITION_OPEN, OVERRIDE_TRANSITION_CLOSE }) + @Retention(RetentionPolicy.SOURCE) public @interface OverrideTransition {} /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 8b4ebaee04c5..854e12177fb5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4998,6 +4998,7 @@ public class ActivityManager { STOP_USER_ON_SWITCH_TRUE, STOP_USER_ON_SWITCH_FALSE }) + @Retention(RetentionPolicy.SOURCE) public @interface StopUserOnSwitch {} /** diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 919e084002ea..a7b29aab4e69 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -22,12 +22,12 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager.InterruptionFilter; -import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.Condition; +import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenPolicy; import android.view.WindowInsetsController; @@ -111,6 +111,7 @@ public final class AutomaticZenRule implements Parcelable { private ComponentName configurationActivity; private long creationTime; private ZenPolicy mZenPolicy; + private ZenDeviceEffects mDeviceEffects; private boolean mModified = false; private String mPkg; private int mType = TYPE_UNKNOWN; @@ -190,6 +191,7 @@ public final class AutomaticZenRule implements Parcelable { /** * @hide */ + // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder). public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity, Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled, long creationTime) { @@ -209,10 +211,11 @@ public final class AutomaticZenRule implements Parcelable { configurationActivity = getTrimmedComponentName( source.readParcelable(null, android.content.ComponentName.class)); creationTime = source.readLong(); - mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); + mZenPolicy = source.readParcelable(null, ZenPolicy.class); mModified = source.readInt() == ENABLED; mPkg = source.readString(); if (Flags.modesApi()) { + mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class); mAllowManualInvocation = source.readBoolean(); mIconResId = source.readInt(); mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); @@ -274,10 +277,18 @@ public final class AutomaticZenRule implements Parcelable { /** * Gets the zen policy. */ + @Nullable public ZenPolicy getZenPolicy() { return mZenPolicy == null ? null : this.mZenPolicy.copy(); } + /** Gets the {@link ZenDeviceEffects} of this rule. */ + @Nullable + @FlaggedApi(Flags.FLAG_MODES_API) + public ZenDeviceEffects getDeviceEffects() { + return mDeviceEffects; + } + /** * Returns the time this rule was created, represented as milliseconds since the epoch. */ @@ -325,11 +336,21 @@ public final class AutomaticZenRule implements Parcelable { /** * Sets the zen policy. */ - public void setZenPolicy(ZenPolicy zenPolicy) { + public void setZenPolicy(@Nullable ZenPolicy zenPolicy) { this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy()); } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to + * the device behavior that should apply while the rule is active, but are not directly related + * to suppressing notifications (for example: disabling always-on display). + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + } + + /** * Sets the configuration activity - an activity that handles * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information * about this rule and/or allows them to configure it. This is required to be non-null for rules @@ -451,6 +472,7 @@ public final class AutomaticZenRule implements Parcelable { dest.writeInt(mModified ? ENABLED : DISABLED); dest.writeString(mPkg); if (Flags.modesApi()) { + dest.writeParcelable(mDeviceEffects, 0); dest.writeBoolean(mAllowManualInvocation); dest.writeInt(mIconResId); dest.writeString(mTriggerDescription); @@ -472,7 +494,8 @@ public final class AutomaticZenRule implements Parcelable { .append(",mZenPolicy=").append(mZenPolicy); if (Flags.modesApi()) { - sb.append(",allowManualInvocation=").append(mAllowManualInvocation) + sb.append(",deviceEffects=").append(mDeviceEffects) + .append(",allowManualInvocation=").append(mAllowManualInvocation) .append(",iconResId=").append(mIconResId) .append(",triggerDescription=").append(mTriggerDescription) .append(",type=").append(mType); @@ -498,6 +521,7 @@ public final class AutomaticZenRule implements Parcelable { && other.creationTime == creationTime; if (Flags.modesApi()) { return finalEquals + && Objects.equals(other.mDeviceEffects, mDeviceEffects) && other.mAllowManualInvocation == mAllowManualInvocation && other.mIconResId == mIconResId && Objects.equals(other.mTriggerDescription, mTriggerDescription) @@ -510,8 +534,8 @@ public final class AutomaticZenRule implements Parcelable { public int hashCode() { if (Flags.modesApi()) { return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, - configurationActivity, mZenPolicy, mModified, creationTime, mPkg, - mAllowManualInvocation, mIconResId, mTriggerDescription, mType); + configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime, + mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType); } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); @@ -573,6 +597,7 @@ public final class AutomaticZenRule implements Parcelable { private boolean mEnabled; private ComponentName mConfigurationActivity = null; private ZenPolicy mPolicy = null; + private ZenDeviceEffects mDeviceEffects = null; private int mType; private String mDescription; private int mIconResId; @@ -588,6 +613,7 @@ public final class AutomaticZenRule implements Parcelable { mEnabled = rule.isEnabled(); mConfigurationActivity = rule.getConfigurationActivity(); mPolicy = rule.getZenPolicy(); + mDeviceEffects = rule.getDeviceEffects(); mType = rule.getType(); mDescription = rule.getTriggerDescription(); mIconResId = rule.getIconResId(); @@ -639,6 +665,17 @@ public final class AutomaticZenRule implements Parcelable { } /** + * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes + * to the device behavior that should apply while the rule is active, but are not directly + * related to suppressing notifications (for example: disabling always-on display). + */ + @NonNull + public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) { + mDeviceEffects = deviceEffects; + return this; + } + + /** * Sets the type of the rule */ public @NonNull Builder setType(@Type int type) { @@ -687,6 +724,7 @@ public final class AutomaticZenRule implements Parcelable { public @NonNull AutomaticZenRule build() { AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, mConditionId, mPolicy, mInterruptionFilter, mEnabled); + rule.mDeviceEffects = mDeviceEffects; rule.creationTime = mCreationTime; rule.mType = mType; rule.mTriggerDescription = mDescription; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ec5effd0963d..94385717d349 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -214,6 +214,8 @@ interface INotificationManager void setNotificationPolicyAccessGranted(String pkg, boolean granted); void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); AutomaticZenRule getAutomaticZenRule(String id); + Map<String, AutomaticZenRule> getAutomaticZenRules(); + // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. List<ZenModeConfig.ZenRule> getZenRules(); String addAutomaticZenRule(in AutomaticZenRule automaticZenRule, String pkg); boolean updateAutomaticZenRule(String id, in AutomaticZenRule automaticZenRule); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 545ba8e81f62..6aad1682466d 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -64,6 +64,8 @@ import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.PasswordValidationError; import com.android.internal.widget.VerifyCredentialResponse; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; @@ -235,6 +237,7 @@ public class KeyguardManager { PIN, PATTERN }) + @Retention(RetentionPolicy.SOURCE) @interface LockTypes {} private final IKeyguardLockedStateListener mIKeyguardLockedStateListener = diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 2d80b1ffca6c..337e3f1195be 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -924,6 +924,7 @@ public class Notification implements Parcelable VISIBILITY_SECRET, NotificationManager.VISIBILITY_NO_OVERRIDE }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationVisibilityOverride{}; /** diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index ffb79b3fc552..51c937d4e94a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1256,17 +1256,21 @@ public class NotificationManager { public Map<String, AutomaticZenRule> getAutomaticZenRules() { INotificationManager service = getService(); try { - List<ZenModeConfig.ZenRule> rules = service.getZenRules(); - Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); - for (ZenModeConfig.ZenRule rule : rules) { - AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, - rule.configurationActivity, rule.conditionId, rule.zenPolicy, - zenModeToInterruptionFilter(rule.zenMode), rule.enabled, - rule.creationTime); - azr.setPackageName(rule.pkg); - ruleMap.put(rule.id, azr); + if (Flags.modesApi()) { + return service.getAutomaticZenRules(); + } else { + List<ZenModeConfig.ZenRule> rules = service.getZenRules(); + Map<String, AutomaticZenRule> ruleMap = new HashMap<>(); + for (ZenModeConfig.ZenRule rule : rules) { + AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, rule.conditionId, rule.zenPolicy, + zenModeToInterruptionFilter(rule.zenMode), rule.enabled, + rule.creationTime); + azr.setPackageName(rule.pkg); + ruleMap.put(rule.id, azr); + } + return ruleMap; } - return ruleMap; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index fa52968e4554..79a5879b5cc0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -113,6 +113,7 @@ import android.hardware.iris.IrisManager; import android.hardware.lights.LightsManager; import android.hardware.lights.SystemLightsManager; import android.hardware.location.ContextHubManager; +import android.hardware.location.IContextHubService; import android.hardware.radio.RadioManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; @@ -1118,8 +1119,12 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<ContextHubManager>() { @Override public ContextHubManager createService(ContextImpl ctx) throws ServiceNotFoundException { - return new ContextHubManager(ctx.getOuterContext(), - ctx.mMainThread.getHandler().getLooper()); + IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE); + if (b == null) { + return null; + } + return new ContextHubManager(IContextHubService.Stub.asInterface(b), + ctx.mMainThread.getHandler().getLooper()); }}); registerService(Context.INCIDENT_SERVICE, IncidentManager.class, diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 019a1a8cb2a6..46216343dcff 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -42,6 +42,8 @@ import android.view.Surface; import android.view.WindowManager; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -120,6 +122,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu WINDOWING_MODE_PINNED, WINDOWING_MODE_FREEFORM, }) + @Retention(RetentionPolicy.SOURCE) public @interface WindowingMode {} /** The current activity type of the configuration. */ @@ -147,6 +150,7 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_DREAM, }) + @Retention(RetentionPolicy.SOURCE) public @interface ActivityType {} /** The current always on top status of the configuration. */ diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index e4ee959336a5..14462b853c02 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -47,6 +47,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; @@ -175,6 +177,7 @@ public final class DeviceAdminInfo implements Parcelable { public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED}) + @Retention(RetentionPolicy.SOURCE) private @interface HeadlessDeviceOwnerMode {} /** @hide */ diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index 84b1ca5c6a61..b0bec783555b 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -164,11 +164,8 @@ public final class DevicePolicyIdentifiers { /** * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. - * - * @hide */ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) - @TestApi public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; /** diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3df11f6f5691..3ee9d69229eb 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3998,8 +3998,7 @@ public class DevicePolicyManager { /** * An integer array extra for {@link #ACTION_DEVICE_POLICY_RESOURCE_UPDATED} to indicate which - * resource IDs (see {@link DevicePolicyResources.Drawables} and - * {@link DevicePolicyResources.Strings}) have been updated. + * resource IDs (i.e. strings and drawables) have been updated. */ public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS"; @@ -8370,9 +8369,7 @@ public class DevicePolicyManager { * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was * successfully set or not. This callback will contain: * <ul> - * <li> The policy identifier returned from - * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction - * {@link UserManager#DISALLOW_CAMERA} + * <li> The policy identifier: userRestriction_no_camera * <li> The {@link TargetUser} that this policy relates to * <li> The {@link PolicyUpdateResult}, which will be * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the @@ -13403,7 +13400,7 @@ public class DevicePolicyManager { * A device owner, by default, may continue granting these permissions. However, for increased * user control, the admin may opt out of controlling grants for these permissions by including * {@link #EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters. - * In that case the device owner's control will be limited do denying these permissions. + * In that case the device owner's control will be limited to denying these permissions. * <p> * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over * the following permissions are restricted for managed profile owners: @@ -17113,19 +17110,19 @@ public class DevicePolicyManager { * Returns {@code true} if this device is marked as a financed device. * * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages}) - * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. + * by the holder of the role {@code android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. * If this occurs, Device Owners and Profile Owners that have set lock task packages or * features, or that attempt to set lock task packages or features, will receive a callback * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and * {@link PolicyUpdateReceiver#onPolicySetResult}. * * <p>To be informed of changes to this status you can subscribe to the broadcast - * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}. + * {@link #ACTION_DEVICE_FINANCING_STATE_CHANGED}. * * @throws SecurityException if the caller is not a device owner, profile owner of an * organization-owned managed profile, profile owner on the primary user or holder of one of the - * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, - * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION. + * following roles: {@code android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, + * {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}. */ public boolean isDeviceFinanced() { throwIfParentInstance("isDeviceFinanced"); diff --git a/core/java/android/app/admin/DevicePolicyResourcesManager.java b/core/java/android/app/admin/DevicePolicyResourcesManager.java index 2cc189f87ced..7a7123167771 100644 --- a/core/java/android/app/admin/DevicePolicyResourcesManager.java +++ b/core/java/android/app/admin/DevicePolicyResourcesManager.java @@ -452,7 +452,7 @@ public class DevicePolicyResourcesManager { /** * Returns the appropriate updated string for the {@code stringId} (see - * {@link DevicePolicyResources.Strings}) if one was set using + * {@code DevicePolicyResources.Strings}) if one was set using * {@code setStrings}, otherwise returns the string from {@code defaultStringLoader}. * * <p>Also returns the string from {@code defaultStringLoader} if {@code stringId} is diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index a6595feed1f7..b5c66ffa72a1 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -86,7 +86,9 @@ public final class AmbientContextEvent implements Parcelable { EVENT_SNORE, EVENT_BACK_DOUBLE_TAP, EVENT_VENDOR_WEARABLE_START, - }) public @interface EventCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface EventCode {} /** The integer indicating an unknown level. */ public static final int LEVEL_UNKNOWN = 0; @@ -114,7 +116,9 @@ public final class AmbientContextEvent implements Parcelable { LEVEL_MEDIUM, LEVEL_MEDIUM_HIGH, LEVEL_HIGH - }) public @interface LevelValue {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LevelValue {} @EventCode private final int mEventType; private static int defaultEventType() { diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java index bf383f1165c4..159481f82e39 100644 --- a/core/java/android/app/ambientcontext/AmbientContextManager.java +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -32,6 +32,8 @@ import android.os.RemoteException; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -106,7 +108,9 @@ public final class AmbientContextManager { STATUS_SERVICE_UNAVAILABLE, STATUS_MICROPHONE_DISABLED, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} /** * Allows clients to retrieve the list of {@link AmbientContextEvent}s from the intent. diff --git a/core/java/android/app/cloudsearch/SearchResponse.java b/core/java/android/app/cloudsearch/SearchResponse.java index c86142e0d22d..dab1657d60d0 100644 --- a/core/java/android/app/cloudsearch/SearchResponse.java +++ b/core/java/android/app/cloudsearch/SearchResponse.java @@ -21,6 +21,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -37,6 +39,7 @@ public final class SearchResponse implements Parcelable { SEARCH_STATUS_OK, SEARCH_STATUS_TIME_OUT, SEARCH_STATUS_NO_INTERNET}) + @Retention(RetentionPolicy.SOURCE) public @interface SearchStatusCode { } diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java index dd332c850d5d..bc8fac5fa0ce 100644 --- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -71,17 +71,13 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @NonNull public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken, @NonNull Configuration config) { - if (config == null) { - throw new IllegalArgumentException("Config must not be null."); - } - ActivityConfigurationChangeItem instance = ObjectPool.obtain(ActivityConfigurationChangeItem.class); if (instance == null) { instance = new ActivityConfigurationChangeItem(); } instance.setActivityToken(activityToken); - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); return instance; } @@ -89,7 +85,7 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem { @Override public void recycle() { super.recycle(); - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java index a5dd115d78b3..3ce094ef7467 100644 --- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java +++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java @@ -32,6 +32,7 @@ import android.util.Slog; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -51,7 +52,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { /** * A record that was properly configured for relaunch. Execution will be cancelled if not - * initialized after {@link #preExecute(ClientTransactionHandler, IBinder)}. + * initialized after {@link #preExecute(ClientTransactionHandler)}. */ private ActivityClientRecord mActivityClientRecord; @@ -99,10 +100,11 @@ public class ActivityRelaunchItem extends ActivityTransactionItem { instance = new ActivityRelaunchItem(); } instance.setActivityToken(activityToken); - instance.mPendingResults = pendingResults; - instance.mPendingNewIntents = pendingNewIntents; + instance.mPendingResults = pendingResults != null ? new ArrayList<>(pendingResults) : null; + instance.mPendingNewIntents = + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null; instance.mConfigChanges = configChanges; - instance.mConfig = config; + instance.mConfig = new MergedConfiguration(config); instance.mPreserveWindow = preserveWindow; return instance; diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java index 24fced4981d6..51a09fb59236 100644 --- a/core/java/android/app/servertransaction/ActivityResultItem.java +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -35,6 +35,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Trace; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -82,7 +83,7 @@ public class ActivityResultItem extends ActivityTransactionItem { instance = new ActivityResultItem(); } instance.setActivityToken(activityToken); - instance.mResultInfoList = resultInfoList; + instance.mResultInfoList = new ArrayList<>(resultInfoList); return instance; } diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java index 2a65b3528145..b4ff476f4702 100644 --- a/core/java/android/app/servertransaction/ActivityTransactionItem.java +++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java @@ -20,6 +20,8 @@ import static android.app.servertransaction.TransactionExecutorHelper.getActivit import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static java.util.Objects.requireNonNull; + import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -93,7 +95,7 @@ public abstract class ActivityTransactionItem extends ClientTransactionItem { } void setActivityToken(@NonNull IBinder activityToken) { - mActivityToken = activityToken; + mActivityToken = requireNonNull(activityToken); } // To be overridden diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index 9c0cd39e8102..7c34cdefe95f 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -54,12 +54,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { /** A list of individual callbacks to a client. */ @UnsupportedAppUsage + @Nullable private List<ClientTransactionItem> mActivityCallbacks; /** * Final lifecycle state in which the client activity should be after the transaction is * executed. */ + @Nullable private ActivityLifecycleItem mLifecycleStateRequest; /** Target client. */ @@ -123,6 +125,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { @VisibleForTesting(visibility = PACKAGE) @UnsupportedAppUsage @Deprecated + @Nullable public ActivityLifecycleItem getLifecycleStateRequest() { return mLifecycleStateRequest; } @@ -207,7 +210,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { for (int i = 0; i < size; i++) { mActivityCallbacks.get(i).recycle(); } - mActivityCallbacks.clear(); + mActivityCallbacks = null; } if (mLifecycleStateRequest != null) { mLifecycleStateRequest.recycle(); diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java index 96961aced987..0e327a7627d1 100644 --- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java +++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java @@ -64,7 +64,7 @@ public class ConfigurationChangeItem extends ClientTransactionItem { if (instance == null) { instance = new ConfigurationChangeItem(); } - instance.mConfiguration = config; + instance.mConfiguration = new Configuration(config); instance.mDeviceId = deviceId; return instance; diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index a64c744c70ba..d2ef65aec698 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -45,6 +45,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -135,10 +136,16 @@ public class LaunchActivityItem extends ClientTransactionItem { if (instance == null) { instance = new LaunchActivityItem(); } - setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId, - referrer, voiceInteractor, procState, state, persistentState, pendingResults, - pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken, - activityClientController, shareableActivityToken, + setValues(instance, activityToken, new Intent(intent), ident, new ActivityInfo(info), + new Configuration(curConfig), new Configuration(overrideConfig), deviceId, + referrer, voiceInteractor, procState, + state != null ? new Bundle(state) : null, + persistentState != null ? new PersistableBundle(persistentState) : null, + pendingResults != null ? new ArrayList<>(pendingResults) : null, + pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null, + activityOptions, isForward, + profilerInfo != null ? new ProfilerInfo(profilerInfo) : null, + assistToken, activityClientController, shareableActivityToken, launchedFromBubble, taskFragmentToken); return instance; diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java index e56d3f862b1b..961da19daeed 100644 --- a/core/java/android/app/servertransaction/MoveToDisplayItem.java +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -69,7 +69,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { } instance.setActivityToken(activityToken); instance.mTargetDisplayId = targetDisplayId; - instance.mConfiguration = configuration; + instance.mConfiguration = new Configuration(configuration); return instance; } @@ -78,7 +78,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem { public void recycle() { super.recycle(); mTargetDisplayId = 0; - mConfiguration = Configuration.EMPTY; + mConfiguration = null; ObjectPool.recycle(this); } diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java index 8e995aa05a48..acf2ea429e82 100644 --- a/core/java/android/app/servertransaction/NewIntentItem.java +++ b/core/java/android/app/servertransaction/NewIntentItem.java @@ -32,6 +32,7 @@ import android.os.Trace; import com.android.internal.content.ReferrerIntent; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -71,7 +72,7 @@ public class NewIntentItem extends ActivityTransactionItem { instance = new NewIntentItem(); } instance.setActivityToken(activityToken); - instance.mIntents = intents; + instance.mIntents = new ArrayList<>(intents); instance.mResume = resume; return instance; diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java index 375d1bf57174..cbad92ff3f38 100644 --- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java +++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java @@ -65,7 +65,7 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem { instance = new WindowContextInfoChangeItem(); } instance.mClientToken = requireNonNull(clientToken); - instance.mInfo = new WindowContextInfo(config, displayId); + instance.mInfo = new WindowContextInfo(new Configuration(config), displayId); return instance; } diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index 98281338872b..7d3eb8783c04 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -77,10 +77,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { instance = new WindowStateResizeItem(); } instance.mWindow = requireNonNull(window); - instance.mFrames = requireNonNull(frames); + instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; - instance.mConfiguration = requireNonNull(configuration); - instance.mInsetsState = requireNonNull(insetsState); + instance.mConfiguration = new MergedConfiguration(configuration); + instance.mInsetsState = new InsetsState(insetsState); instance.mForceLayout = forceLayout; instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; instance.mDisplayId = displayId; diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index d0b4fe473148..f1ca0864a6dd 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -35,6 +35,8 @@ import android.os.SharedMemory; import android.service.wearable.WearableSensingService; import android.system.OsConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -105,7 +107,9 @@ public class WearableSensingManager { STATUS_SERVICE_UNAVAILABLE, STATUS_WEARABLE_UNAVAILABLE, STATUS_ACCESS_DENIED - }) public @interface StatusCode {} + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusCode {} private final Context mContext; private final IWearableSensingManager mService; diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig new file mode 100644 index 000000000000..6a735a418b58 --- /dev/null +++ b/core/java/android/appwidget/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.appwidget.flags" + +flag { + name: "generated_previews" + namespace: "app_widgets" + description: "Enable support for generated previews in AppWidgetManager" + bug: "306546610" +} diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 9ea3dfc61ef1..e0ce917fa3b3 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -33,6 +33,7 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UserHandleAware; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -795,9 +796,19 @@ public final class CompanionDeviceManager { @UserHandleAware @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public @NonNull List<AssociationInfo> getAllAssociations() { + return getAllAssociations(mContext.getUserId()); + } + + /** + * Per-user version of {@link #getAllAssociations()}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public @NonNull List<AssociationInfo> getAllAssociations(@UserIdInt int userId) { if (!checkFeaturePresent()) return Collections.emptyList(); try { - return mService.getAllAssociationsForUser(mContext.getUserId()); + return mService.getAllAssociationsForUser(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -830,12 +841,25 @@ public final class CompanionDeviceManager { @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void addOnAssociationsChangedListener( @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener) { + addOnAssociationsChangedListener(executor, listener, mContext.getUserId()); + } + + /** + * Per-user version of + * {@link #addOnAssociationsChangedListener(Executor, OnAssociationsChangedListener)}. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) + public void addOnAssociationsChangedListener( + @NonNull Executor executor, @NonNull OnAssociationsChangedListener listener, + @UserIdInt int userId) { if (!checkFeaturePresent()) return; synchronized (mListeners) { final OnAssociationsChangedListenerProxy proxy = new OnAssociationsChangedListenerProxy( executor, listener); try { - mService.addOnAssociationsChangedListener(proxy, mContext.getUserId()); + mService.addOnAssociationsChangedListener(proxy, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 2f97080901f9..102cbf3a7e31 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -23,8 +23,7 @@ import android.companion.virtual.audio.IAudioRoutingCallback; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorConfig; import android.companion.virtual.sensor.VirtualSensorEvent; -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; +import android.companion.virtual.camera.VirtualCameraConfig; import android.content.ComponentName; import android.content.IntentFilter; import android.graphics.Point; @@ -236,8 +235,15 @@ interface IVirtualDevice { void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor); /** - * Creates a new VirtualCamera and registers it with the VirtualCameraProvider. + * Creates a new virtual camera and registers it with the virtual camera service. */ @EnforcePermission("CREATE_VIRTUAL_DEVICE") - void registerVirtualCamera(in IVirtualCamera camera); + void registerVirtualCamera(in VirtualCameraConfig camera); + + /** + * Destroys the virtual camera with given config and unregisters it from the virtual camera + * service. + */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void unregisterVirtualCamera(in VirtualCameraConfig camera); } diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index 93a3e7822888..d0c8be6ca896 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -32,9 +32,8 @@ import android.os.RemoteException; * Details of a particular virtual device. * * <p>Read-only device representation exposing the properties of an existing virtual device. - * - * @see VirtualDeviceManager#registerVirtualDeviceListener */ +// TODO(b/310912420): Link to VirtualDeviceManager#registerVirtualDeviceListener from the docs public final class VirtualDevice implements Parcelable { private final @NonNull IVirtualDevice mVirtualDevice; @@ -92,8 +91,8 @@ public final class VirtualDevice implements Parcelable { * per device. * * @see Context#createDeviceContext - * @see #getPersistentDeviceId */ + // TODO(b/310912420): Link to #getPersistentDeviceId from the docs public int getDeviceId() { return mId; } diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index f6a7d2a465fb..da8277c24f6c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -271,7 +271,7 @@ public class VirtualDeviceInternal { final IBinder token = new Binder( "android.hardware.input.VirtualDpad:" + config.getInputDeviceName()); mVirtualDevice.createVirtualDpad(config, token); - return new VirtualDpad(mVirtualDevice, token); + return new VirtualDpad(config, mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -283,7 +283,7 @@ public class VirtualDeviceInternal { final IBinder token = new Binder( "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName()); mVirtualDevice.createVirtualKeyboard(config, token); - return new VirtualKeyboard(mVirtualDevice, token); + return new VirtualKeyboard(config, mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -295,7 +295,7 @@ public class VirtualDeviceInternal { final IBinder token = new Binder( "android.hardware.input.VirtualMouse:" + config.getInputDeviceName()); mVirtualDevice.createVirtualMouse(config, token); - return new VirtualMouse(mVirtualDevice, token); + return new VirtualMouse(config, mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -308,7 +308,7 @@ public class VirtualDeviceInternal { final IBinder token = new Binder( "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName()); mVirtualDevice.createVirtualTouchscreen(config, token); - return new VirtualTouchscreen(mVirtualDevice, token); + return new VirtualTouchscreen(config, mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -322,7 +322,7 @@ public class VirtualDeviceInternal { "android.hardware.input.VirtualNavigationTouchpad:" + config.getInputDeviceName()); mVirtualDevice.createVirtualNavigationTouchpad(config, token); - return new VirtualNavigationTouchpad(mVirtualDevice, token); + return new VirtualNavigationTouchpad(config, mVirtualDevice, token); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index b3ea93bb8a85..41c90b96dc84 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -149,6 +149,19 @@ public final class VirtualDeviceManager { @SystemApi public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; + /** + * Persistent device identifier corresponding to the default device. + * + * @see Context#DEVICE_ID_DEFAULT + * @see VirtualDevice#getPersistentDeviceId() + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API) + public static final String PERSISTENT_DEVICE_ID_DEFAULT = + "default:" + Context.DEVICE_ID_DEFAULT; + private final IVirtualDeviceManager mService; private final Context mContext; @@ -199,9 +212,10 @@ public final class VirtualDeviceManager { * existing virtual devices.</p> * * <p>Note that if a virtual device is closed and becomes invalid, the returned objects will - * not be updated and may contain stale values. Use a {@link VirtualDeviceListener} for real - * time updates of the availability of virtual devices.</p> + * not be updated and may contain stale values.</p> */ + // TODO(b/310912420): Add "Use a VirtualDeviceListener for real time updates of the + // availability of virtual devices." in the note paragraph above with a link annotation. @NonNull public List<android.companion.virtual.VirtualDevice> getVirtualDevices() { if (mService == null) { diff --git a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl b/core/java/android/companion/virtual/camera/IVirtualCamera.aidl deleted file mode 100644 index 58b850dac41c..000000000000 --- a/core/java/android/companion/virtual/camera/IVirtualCamera.aidl +++ /dev/null @@ -1,17 +0,0 @@ -package android.companion.virtual.camera; - -import android.companion.virtual.camera.IVirtualCameraSession; -import android.companion.virtual.camera.VirtualCameraHalConfig; - -/** - * Counterpart of ICameraDevice for virtual camera. - * - * @hide - */ -interface IVirtualCamera { - - IVirtualCameraSession open(); - - VirtualCameraHalConfig getHalConfig(); - -}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl new file mode 100644 index 000000000000..fac44b50024f --- /dev/null +++ b/core/java/android/companion/virtual/camera/IVirtualCameraCallback.aidl @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtual.camera.VirtualCameraMetadata; +import android.view.Surface; + +/** + * Interface for the virtual camera service and system server to talk back to the virtual camera owner. + * + * @hide + */ +interface IVirtualCameraCallback { + + /** + * Called when one of the requested stream has been configured by the virtual camera service and + * is ready to receive data onto its {@link Surface} + * + * @param streamId The id of the configured stream + * @param surface The surface to write data into for this stream + * @param streamConfig The image data configuration for this stream + */ + oneway void onStreamConfigured( + int streamId, + in Surface surface, + in VirtualCameraStreamConfig streamConfig); + + /** + * The client application is requesting a camera frame for the given streamId with the provided + * metadata. + * + * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to + * this stream that was provided during the {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} call. + * + * @param streamId The streamId for which the frame is requested. This corresponds to the + * streamId that was given in {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} + * @param frameId The frameId that is being requested. Each request will have a different + * frameId, that will be increasing for each call with a particular streamId. + * @param metadata The metadata requested for the frame. The virtual camera should do its best + * to honor the requested metadata. + */ + oneway void onProcessCaptureRequest( + int streamId, long frameId, in VirtualCameraMetadata metadata); + + /** + * The stream previously configured when {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be + * freed. The Surface was disposed on the client side and should not be used anymore by the virtual camera owner + * + * @param streamId The id of the stream that was closed. + */ + oneway void onStreamClosed(int streamId); + +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCamera.java b/core/java/android/companion/virtual/camera/VirtualCamera.java index 791bf0a1ff1f..beee86fcfac2 100644 --- a/core/java/android/companion/virtual/camera/VirtualCamera.java +++ b/core/java/android/companion/virtual/camera/VirtualCamera.java @@ -16,20 +16,44 @@ package android.companion.virtual.camera; +import android.annotation.FlaggedApi; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.flags.Flags; +import android.hardware.camera2.CameraDevice; import android.os.RemoteException; import androidx.annotation.NonNull; +import java.io.Closeable; import java.util.Objects; +import java.util.concurrent.Executor; /** - * Virtual camera that is used to send image data into system. + * A VirtualCamera is the representation of a remote or computer generated camera that will be + * exposed to applications using the Android Camera APIs. * + * <p>A VirtualCamera is created using {@link + * VirtualDeviceManager.VirtualDevice#createVirtualCamera(VirtualCameraConfig)}. + * + * <p>Once a virtual camera is created, it will receive callbacks from the system when an + * application attempts to use it via the {@link VirtualCameraCallback} class set using {@link + * VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback)} + * + * @see VirtualDeviceManager.VirtualDevice#createVirtualDevice(int, VirtualDeviceParams) + * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback) + * @see android.hardware.camera2.CameraManager#openCamera(String, CameraDevice.StateCallback, + * android.os.Handler) * @hide */ -public final class VirtualCamera extends IVirtualCamera.Stub { +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCamera implements Closeable { + private final IVirtualDevice mVirtualDevice; private final VirtualCameraConfig mConfig; /** @@ -37,40 +61,35 @@ public final class VirtualCamera extends IVirtualCamera.Stub { * * @param virtualDevice The Binder object representing this camera in the server. * @param config Configuration for the new virtual camera + * @hide */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public VirtualCamera( @NonNull IVirtualDevice virtualDevice, @NonNull VirtualCameraConfig config) { + mVirtualDevice = virtualDevice; mConfig = Objects.requireNonNull(config); Objects.requireNonNull(virtualDevice); + // TODO(b/310857519): Avoid registration inside constructor. try { - virtualDevice.registerVirtualCamera(this); + mVirtualDevice.registerVirtualCamera(config); } catch (RemoteException e) { e.rethrowFromSystemServer(); } } - /** Get the camera session associated with this device */ - @Override - public IVirtualCameraSession open() { - // TODO: b/302255544 - Make this async. - VirtualCameraSession session = mConfig.getCallback().onOpenSession(); - return new VirtualCameraSessionInternal(session); - } - /** Returns the configuration of this virtual camera instance. */ @NonNull public VirtualCameraConfig getConfig() { return mConfig; } - /** - * Returns the configuration to be used by the virtual camera HAL. - * - * @hide - */ @Override - @NonNull - public VirtualCameraHalConfig getHalConfig() { - return mConfig.getHalConfig(); + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void close() { + try { + mVirtualDevice.unregisterVirtualCamera(mConfig); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java index a7c3d4fac7dc..a18ae03555e9 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraCallback.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraCallback.java @@ -16,7 +16,12 @@ package android.companion.virtual.camera; -import android.hardware.camera2.params.SessionConfiguration; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.view.Surface; import java.util.concurrent.Executor; @@ -24,15 +29,53 @@ import java.util.concurrent.Executor; * Interface to be provided when creating a new {@link VirtualCamera} in order to receive callbacks * from the framework and the camera system. * - * @see VirtualCameraConfig.Builder#setCallback(Executor, VirtualCameraCallback) + * @see VirtualCameraConfig.Builder#setVirtualCameraCallback(Executor, VirtualCameraCallback) * @hide */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public interface VirtualCameraCallback { /** - * Called when a client opens a new camera session for the associated {@link VirtualCamera} + * Called when one of the requested stream has been configured by the virtual camera service and + * is ready to receive data onto its {@link Surface} * - * @see android.hardware.camera2.CameraDevice#createCaptureSession(SessionConfiguration) + * @param streamId The id of the configured stream + * @param surface The surface to write data into for this stream + * @param streamConfig The image data configuration for this stream */ - VirtualCameraSession onOpenSession(); + void onStreamConfigured( + int streamId, + @NonNull Surface surface, + @NonNull VirtualCameraStreamConfig streamConfig); + + /** + * The client application is requesting a camera frame for the given streamId with the provided + * metadata. + * + * <p>The virtual camera needs to write the frame data in the {@link Surface} corresponding to + * this stream that was provided during the {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} call. + * + * @param streamId The streamId for which the frame is requested. This corresponds to the + * streamId that was given in {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} + * @param frameId The frameId that is being requested. Each request will have a different + * frameId, that will be increasing for each call with a particular streamId. + * @param metadata The metadata requested for the frame. The virtual camera should do its best + * to honor the requested metadata but the consumer won't be informed about the metadata set + * for a particular frame. If null, the requested frame can be anything the producer sends. + */ + void onProcessCaptureRequest( + int streamId, long frameId, @Nullable VirtualCameraMetadata metadata); + + /** + * The stream previously configured when {@link #onStreamConfigured(int, Surface, + * VirtualCameraStreamConfig)} was called is now being closed and associated resources can be + * freed. The Surface corresponding to that streamId was disposed on the client side and should + * not be used anymore by the virtual camera owner + * + * @param streamId The id of the stream that was closed. + */ + void onStreamClosed(int streamId); } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSession.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl index c25d97711e75..88c27a52bb9d 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraSession.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.aidl @@ -13,18 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.companion.virtual.camera; -/*** - * Counterpart of {@link android.hardware.camera2.CameraCaptureSession} for producing - * images from a {@link VirtualCamera}. - * @hide - */ -// TODO: b/289881985 - This is just a POC implementation for now, this will be extended -// to a full featured Camera Session -public interface VirtualCameraSession { - - /** Close the session and release its resources. */ - default void close() {} -} +/** @hide */ +parcelable VirtualCameraConfig;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java index fb464d53b9b5..f1eb240301e0 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java +++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java @@ -18,11 +18,19 @@ package android.companion.virtual.camera; import static java.util.Objects.requireNonNull; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.StringRes; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.content.res.Resources; import android.graphics.ImageFormat; +import android.os.Parcel; +import android.os.Parcelable; import android.util.ArraySet; +import android.view.Surface; -import java.util.Collections; import java.util.Set; import java.util.concurrent.Executor; @@ -33,33 +41,115 @@ import java.util.concurrent.Executor; * * @hide */ -public final class VirtualCameraConfig { +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraConfig implements Parcelable { - private final String mDisplayName; + private final @StringRes int mNameStringRes; private final Set<VirtualCameraStreamConfig> mStreamConfigurations; - private final VirtualCameraCallback mCallback; - private final Executor mCallbackExecutor; + private final IVirtualCameraCallback mCallback; + + private VirtualCameraConfig( + int displayNameStringRes, + @NonNull Set<VirtualCameraStreamConfig> streamConfigurations, + @NonNull Executor executor, + @NonNull VirtualCameraCallback callback) { + mNameStringRes = displayNameStringRes; + mStreamConfigurations = + Set.copyOf(requireNonNull(streamConfigurations, "Missing stream configurations")); + if (mStreamConfigurations.isEmpty()) { + throw new IllegalArgumentException( + "At least one stream configuration is needed to create a virtual camera."); + } + mCallback = + new VirtualCameraCallbackInternal( + requireNonNull(callback, "Missing callback"), + requireNonNull(executor, "Missing callback executor")); + } + + private VirtualCameraConfig(@NonNull Parcel in) { + mNameStringRes = in.readInt(); + mCallback = IVirtualCameraCallback.Stub.asInterface(in.readStrongBinder()); + mStreamConfigurations = + Set.of( + in.readParcelableArray( + VirtualCameraStreamConfig.class.getClassLoader(), + VirtualCameraStreamConfig.class)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mNameStringRes); + dest.writeStrongInterface(mCallback); + dest.writeParcelableArray( + mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]), flags); + } + + /** + * @return The display name of this VirtualCamera + */ + @StringRes + public int getDisplayNameStringRes() { + return mNameStringRes; + } + + /** + * Returns an unmodifiable set of the stream configurations added to this {@link + * VirtualCameraConfig}. + * + * @see VirtualCameraConfig.Builder#addStreamConfig(int, int, int) + */ + @NonNull + public Set<VirtualCameraStreamConfig> getStreamConfigs() { + return mStreamConfigurations; + } + + /** + * Returns the callback used to communicate from the server to the client. + * + * @hide + */ + @NonNull + public IVirtualCameraCallback getCallback() { + return mCallback; + } /** * Builder for {@link VirtualCameraConfig}. * * <p>To build an instance of {@link VirtualCameraConfig} the following conditions must be met: - * <li>At least one stream must be added wit {@link #addStreamConfiguration(int, int, int)}. - * <li>A name must be set with {@link #setDisplayName(String)} - * <li>A callback must be set wit {@link #setCallback(Executor, VirtualCameraCallback)} + * <li>At least one stream must be added with {@link #addStreamConfig(int, int, int)}. + * <li>A callback must be set with {@link #setVirtualCameraCallback(Executor, + * VirtualCameraCallback)} + * <li>A user readable name can be set with {@link #setDisplayNameStringRes(int)} */ + @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public static final class Builder { - private String mDisplayName; - private final ArraySet<VirtualCameraStreamConfig> mStreamConfiguration = new ArraySet<>(); + private @StringRes int mDisplayNameStringRes = Resources.ID_NULL; + private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>(); private Executor mCallbackExecutor; private VirtualCameraCallback mCallback; - /** Set the visible name of this camera for the user. */ - // TODO: b/290172356 - Take a resource id instead of displayName + /** + * Set the visible name of this camera for the user. + * + * <p>Sets the resource to a string representing a user readable name for this virtual + * camera. + * + * @throws IllegalArgumentException if an invalid resource id is passed. + */ @NonNull - public Builder setDisplayName(@NonNull String displayName) { - mDisplayName = requireNonNull(displayName); + public Builder setDisplayNameStringRes(@StringRes int displayNameStringRes) { + if (displayNameStringRes <= 0) { + throw new IllegalArgumentException("Invalid resource passed for display name"); + } + mDisplayNameStringRes = displayNameStringRes; return this; } @@ -68,18 +158,21 @@ public final class VirtualCameraConfig { * * <p>At least one {@link VirtualCameraStreamConfig} must be added. * - * @param width The width of the stream - * @param height The height of the stream - * @param format The {@link ImageFormat} of the stream + * @param width The width of the stream. + * @param height The height of the stream. + * @param format The {@link ImageFormat} of the stream. + * + * @throws IllegalArgumentException if invalid format or dimensions are passed. */ @NonNull - public Builder addStreamConfiguration( - int width, int height, @ImageFormat.Format int format) { - VirtualCameraStreamConfig streamConfig = new VirtualCameraStreamConfig(); - streamConfig.width = width; - streamConfig.height = height; - streamConfig.format = format; - mStreamConfiguration.add(streamConfig); + public Builder addStreamConfig(int width, int height, @ImageFormat.Format int format) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Invalid dimensions passed for stream config"); + } + if (!ImageFormat.isPublicFormat(format)) { + throw new IllegalArgumentException("Invalid format passed for stream config"); + } + mStreamConfigurations.add(new VirtualCameraStreamConfig(width, height, format)); return this; } @@ -93,7 +186,9 @@ public final class VirtualCameraConfig { * @param callback The instance of the callback to be added. Subsequent call to this method * will replace the callback set. */ - public Builder setCallback( + @NonNull + @SuppressLint("MissingGetterMatchingBuilder") // The configuration is immutable + public Builder setVirtualCameraCallback( @NonNull Executor executor, @NonNull VirtualCameraCallback callback) { mCallbackExecutor = requireNonNull(executor); mCallback = requireNonNull(callback); @@ -108,67 +203,49 @@ public final class VirtualCameraConfig { @NonNull public VirtualCameraConfig build() { return new VirtualCameraConfig( - mDisplayName, mStreamConfiguration, mCallbackExecutor, mCallback); + mDisplayNameStringRes, mStreamConfigurations, mCallbackExecutor, mCallback); } } - private VirtualCameraConfig( - @NonNull String displayName, - @NonNull Set<VirtualCameraStreamConfig> streamConfigurations, - @NonNull Executor executor, - @NonNull VirtualCameraCallback callback) { - mDisplayName = requireNonNull(displayName, "Missing display name"); - mStreamConfigurations = - Collections.unmodifiableSet( - requireNonNull(streamConfigurations, "Missing stream configuration")); - if (mStreamConfigurations.isEmpty()) { - throw new IllegalArgumentException( - "At least one StreamConfiguration is needed to create a virtual camera."); - } - mCallback = requireNonNull(callback, "Missing callback"); - mCallbackExecutor = requireNonNull(executor, "Missing callback executor"); - } + private static class VirtualCameraCallbackInternal extends IVirtualCameraCallback.Stub { - /** - * @return The display name of this VirtualCamera - */ - @NonNull - public String getDisplayName() { - return mDisplayName; - } + private final VirtualCameraCallback mCallback; + private final Executor mExecutor; - /** - * Returns an unmodifiable set of the stream configurations added to this {@link - * VirtualCameraConfig}. - * - * @see VirtualCameraConfig.Builder#addStreamConfiguration(int, int, int) - */ - @NonNull - public Set<VirtualCameraStreamConfig> getStreamConfigs() { - return mStreamConfigurations; - } + private VirtualCameraCallbackInternal(VirtualCameraCallback callback, Executor executor) { + mCallback = callback; + mExecutor = executor; + } - /** Returns the callback used to communicate from the server to the client. */ - @NonNull - public VirtualCameraCallback getCallback() { - return mCallback; - } + @Override + public void onStreamConfigured( + int streamId, Surface surface, VirtualCameraStreamConfig streamConfig) { + mExecutor.execute(() -> mCallback.onStreamConfigured(streamId, surface, streamConfig)); + } - /** Returns the executor onto which the callback should be run. */ - @NonNull - public Executor getCallbackExecutor() { - return mCallbackExecutor; + @Override + public void onProcessCaptureRequest( + int streamId, long frameId, VirtualCameraMetadata metadata) { + mExecutor.execute(() -> mCallback.onProcessCaptureRequest(streamId, frameId, metadata)); + } + + @Override + public void onStreamClosed(int streamId) { + mExecutor.execute(() -> mCallback.onStreamClosed(streamId)); + } } - /** - * Returns a new instance of {@link VirtualCameraHalConfig} initialized with data from this - * {@link VirtualCameraConfig} - */ @NonNull - public VirtualCameraHalConfig getHalConfig() { - VirtualCameraHalConfig halConfig = new VirtualCameraHalConfig(); - halConfig.displayName = mDisplayName; - halConfig.streamConfigs = mStreamConfigurations.toArray(new VirtualCameraStreamConfig[0]); - return halConfig; - } + public static final Parcelable.Creator<VirtualCameraConfig> CREATOR = + new Parcelable.Creator<>() { + @Override + public VirtualCameraConfig createFromParcel(Parcel in) { + return new VirtualCameraConfig(in); + } + + @Override + public VirtualCameraConfig[] newArray(int size) { + return new VirtualCameraConfig[size]; + } + }; } diff --git a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl deleted file mode 100644 index 7070a38b6414..000000000000 --- a/core/java/android/companion/virtual/camera/VirtualCameraHalConfig.aidl +++ /dev/null @@ -1,12 +0,0 @@ -package android.companion.virtual.camera; - -import android.companion.virtual.camera.VirtualCameraStreamConfig; - -/** - * Configuration for VirtualCamera to be passed to the server and HAL service. - * @hide - */ -parcelable VirtualCameraHalConfig { - String displayName; - VirtualCameraStreamConfig[] streamConfigs; -} diff --git a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl index 252980773264..6c1f0fcd622a 100644 --- a/core/java/android/companion/virtual/camera/IVirtualCameraSession.aidl +++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.aidl @@ -13,16 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.companion.virtual.camera; /** - * Counterpart of ICameraDeviceSession for virtual camera. - * + * Data structure used to store {@link android.hardware.camera2.CameraMetadata} compatible with + * VirtualCamera. * @hide */ -interface IVirtualCameraSession { - - void configureStream(int width, int height, int format); - - void close(); -} +parcelable VirtualCameraMetadata; diff --git a/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java new file mode 100644 index 000000000000..1ba36d08cbeb --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraMetadata.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.companion.virtual.camera; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data structure used to store camera metadata compatible with VirtualCamera. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraMetadata implements Parcelable { + + /** @hide */ + public VirtualCameraMetadata(@NonNull Parcel in) {} + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) {} + + @NonNull + public static final Creator<VirtualCameraMetadata> CREATOR = + new Creator<>() { + @Override + @NonNull + public VirtualCameraMetadata createFromParcel(Parcel in) { + return new VirtualCameraMetadata(in); + } + + @Override + @NonNull + public VirtualCameraMetadata[] newArray(int size) { + return new VirtualCameraMetadata[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java b/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java deleted file mode 100644 index da168de41cee..000000000000 --- a/core/java/android/companion/virtual/camera/VirtualCameraSessionInternal.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.companion.virtual.camera; - -import android.graphics.ImageFormat; - -import androidx.annotation.NonNull; - -import java.util.Objects; - -/** - * Wraps the client side {@link VirtualCameraSession} into an {@link IVirtualCameraSession}. - * - * @hide - */ -final class VirtualCameraSessionInternal extends IVirtualCameraSession.Stub { - - @SuppressWarnings("FieldCanBeLocal") - // TODO: b/289881985: Will be used once connected with the CameraService - private final VirtualCameraSession mVirtualCameraSession; - - VirtualCameraSessionInternal(@NonNull VirtualCameraSession virtualCameraSession) { - mVirtualCameraSession = Objects.requireNonNull(virtualCameraSession); - } - - @Override - public void configureStream(int width, int height, @ImageFormat.Format int format) {} - - public void close() {} -} diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl index 304d4553bd2a..ce92b6d48f0d 100644 --- a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.aidl @@ -16,11 +16,7 @@ package android.companion.virtual.camera; /** - * A stream configuration supported by a virtual camera + * The configuration of a single virtual camera stream. * @hide */ -parcelable VirtualCameraStreamConfig { - int width; - int height; - int format; -} +parcelable VirtualCameraStreamConfig;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java new file mode 100644 index 000000000000..e198821e860e --- /dev/null +++ b/core/java/android/companion/virtual/camera/VirtualCameraStreamConfig.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.companion.virtual.camera; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.companion.virtual.flags.Flags; +import android.graphics.ImageFormat; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * The configuration of a single virtual camera stream. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) +public final class VirtualCameraStreamConfig implements Parcelable { + + private final int mWidth; + private final int mHeight; + private final int mFormat; + + /** + * Construct a new instance of {@link VirtualCameraStreamConfig} initialized with the provided + * width, height and {@link ImageFormat} + * + * @param width The width of the stream. + * @param height The height of the stream. + * @param format The {@link ImageFormat} of the stream. + */ + public VirtualCameraStreamConfig( + @IntRange(from = 1) int width, + @IntRange(from = 1) int height, + @ImageFormat.Format int format) { + this.mWidth = width; + this.mHeight = height; + this.mFormat = format; + } + + private VirtualCameraStreamConfig(@NonNull Parcel in) { + mWidth = in.readInt(); + mHeight = in.readInt(); + mFormat = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mFormat); + } + + @NonNull + public static final Creator<VirtualCameraStreamConfig> CREATOR = + new Creator<>() { + @Override + public VirtualCameraStreamConfig createFromParcel(Parcel in) { + return new VirtualCameraStreamConfig(in); + } + + @Override + public VirtualCameraStreamConfig[] newArray(int size) { + return new VirtualCameraStreamConfig[size]; + } + }; + + /** Returns the width of this stream. */ + @IntRange(from = 1) + public int getWidth() { + return mWidth; + } + + /** Returns the height of this stream. */ + @IntRange(from = 1) + public int getHeight() { + return mHeight; + } + + /** Returns the {@link ImageFormat} of this stream. */ + @ImageFormat.Format + public int getFormat() { + return mFormat; + } +} diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index cfab9ebec593..02066fa8a34e 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -58,6 +58,13 @@ flag { } flag { + name: "persistent_device_id_api" + namespace: "virtual_devices" + description: "Enable persistent device ID notification API" + bug: "295258915" +} + +flag { name: "express_metrics" namespace: "virtual_devices" description: "Enable express metrics in VDM" diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ea54c912d4b9..6b39f266a802 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2800,6 +2800,12 @@ public class Intent implements Parcelable, Cloneable { /** * Broadcast Action: An application package that was previously in the stopped state has been * started and is no longer considered stopped. + * <p>When a package is force-stopped, the {@link #ACTION_PACKAGE_RESTARTED} broadcast is sent + * and the package in the stopped state cannot self-start for any reason unless there's an + * explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED} + * broadcast is sent when such an explicit process start occurs and the package is taken + * out of the stopped state. + * </p> * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime() @@ -2807,6 +2813,9 @@ public class Intent implements Parcelable, Cloneable { * </ul> * * <p class="note">This is a protected intent that can only be sent by the system. + * + * @see ApplicationInfo#FLAG_STOPPED + * @see #ACTION_PACKAGE_RESTARTED */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED) diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 884d463e929d..f532c4cffcc4 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -62,6 +62,8 @@ public final class UserProperties implements Parcelable { "mediaSharedWithParent"; private static final String ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT = "credentialShareableWithParent"; + private static final String ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = + "authAlwaysRequiredToDisableQuietMode"; private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent"; private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible"; @@ -80,6 +82,7 @@ public final class UserProperties implements Parcelable { INDEX_DELETE_APP_WITH_PARENT, INDEX_ALWAYS_VISIBLE, INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE, + INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -97,6 +100,7 @@ public final class UserProperties implements Parcelable { private static final int INDEX_DELETE_APP_WITH_PARENT = 10; private static final int INDEX_ALWAYS_VISIBLE = 11; private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12; + private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -329,6 +333,8 @@ public final class UserProperties implements Parcelable { setShowInSettings(orig.getShowInSettings()); setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode()); setUseParentsContacts(orig.getUseParentsContacts()); + setAuthAlwaysRequiredToDisableQuietMode( + orig.isAuthAlwaysRequiredToDisableQuietMode()); } if (hasQueryOrManagePermission) { // Add items that require QUERY_USERS or stronger. @@ -611,6 +617,31 @@ public final class UserProperties implements Parcelable { } private boolean mCredentialShareableWithParent; + /** + * Returns whether the profile always requires user authentication to disable from quiet mode. + * + * <p> Settings this field to true will ensure that the credential confirmation activity is + * always shown whenever the user requests to disable quiet mode. The behavior of credential + * checks is not guaranteed when the property is false and may vary depending on user types. + * @hide + */ + public boolean isAuthAlwaysRequiredToDisableQuietMode() { + if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) { + return mAuthAlwaysRequiredToDisableQuietMode; + } + if (mDefaultProperties != null) { + return mDefaultProperties.mAuthAlwaysRequiredToDisableQuietMode; + } + throw new SecurityException( + "You don't have permission to query authAlwaysRequiredToDisableQuietMode"); + } + /** @hide */ + public void setAuthAlwaysRequiredToDisableQuietMode(boolean val) { + this.mAuthAlwaysRequiredToDisableQuietMode = val; + setPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE); + } + private boolean mAuthAlwaysRequiredToDisableQuietMode; + /* Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during OTA update between user-parent @@ -693,6 +724,8 @@ public final class UserProperties implements Parcelable { + getCrossProfileIntentResolutionStrategy() + ", mMediaSharedWithParent=" + isMediaSharedWithParent() + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent() + + ", mAuthAlwaysRequiredToDisableQuietMode=" + + isAuthAlwaysRequiredToDisableQuietMode() + ", mDeleteAppWithParent=" + getDeleteAppWithParent() + ", mAlwaysVisible=" + getAlwaysVisible() + "}"; @@ -720,6 +753,8 @@ public final class UserProperties implements Parcelable { pw.println(prefix + " mMediaSharedWithParent=" + isMediaSharedWithParent()); pw.println(prefix + " mCredentialShareableWithParent=" + isCredentialShareableWithParent()); + pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode=" + + isAuthAlwaysRequiredToDisableQuietMode()); pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent()); pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible()); } @@ -788,6 +823,9 @@ public final class UserProperties implements Parcelable { case ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT: setCredentialShareableWithParent(parser.getAttributeBoolean(i)); break; + case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE: + setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i)); + break; case ATTR_DELETE_APP_WITH_PARENT: setDeleteAppWithParent(parser.getAttributeBoolean(i)); break; @@ -853,6 +891,10 @@ public final class UserProperties implements Parcelable { serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT, mCredentialShareableWithParent); } + if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) { + serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, + mAuthAlwaysRequiredToDisableQuietMode); + } if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) { serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT, mDeleteAppWithParent); @@ -878,6 +920,7 @@ public final class UserProperties implements Parcelable { dest.writeInt(mCrossProfileIntentResolutionStrategy); dest.writeBoolean(mMediaSharedWithParent); dest.writeBoolean(mCredentialShareableWithParent); + dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode); dest.writeBoolean(mDeleteAppWithParent); dest.writeBoolean(mAlwaysVisible); } @@ -901,6 +944,7 @@ public final class UserProperties implements Parcelable { mCrossProfileIntentResolutionStrategy = source.readInt(); mMediaSharedWithParent = source.readBoolean(); mCredentialShareableWithParent = source.readBoolean(); + mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean(); mDeleteAppWithParent = source.readBoolean(); mAlwaysVisible = source.readBoolean(); } @@ -941,6 +985,7 @@ public final class UserProperties implements Parcelable { CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT; private boolean mMediaSharedWithParent = false; private boolean mCredentialShareableWithParent = false; + private boolean mAuthAlwaysRequiredToDisableQuietMode = false; private boolean mDeleteAppWithParent = false; private boolean mAlwaysVisible = false; @@ -1010,6 +1055,14 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */ + public Builder setAuthAlwaysRequiredToDisableQuietMode( + boolean authAlwaysRequiredToDisableQuietMode) { + mAuthAlwaysRequiredToDisableQuietMode = + authAlwaysRequiredToDisableQuietMode; + return this; + } + /** Sets the value for {@link #mDeleteAppWithParent}*/ public Builder setDeleteAppWithParent(boolean deleteAppWithParent) { mDeleteAppWithParent = deleteAppWithParent; @@ -1036,6 +1089,7 @@ public final class UserProperties implements Parcelable { mCrossProfileIntentResolutionStrategy, mMediaSharedWithParent, mCredentialShareableWithParent, + mAuthAlwaysRequiredToDisableQuietMode, mDeleteAppWithParent, mAlwaysVisible); } @@ -1053,6 +1107,7 @@ public final class UserProperties implements Parcelable { @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy, boolean mediaSharedWithParent, boolean credentialShareableWithParent, + boolean authAlwaysRequiredToDisableQuietMode, boolean deleteAppWithParent, boolean alwaysVisible) { mDefaultProperties = null; @@ -1067,6 +1122,8 @@ public final class UserProperties implements Parcelable { setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy); setMediaSharedWithParent(mediaSharedWithParent); setCredentialShareableWithParent(credentialShareableWithParent); + setAuthAlwaysRequiredToDisableQuietMode( + authAlwaysRequiredToDisableQuietMode); setDeleteAppWithParent(deleteAppWithParent); setAlwaysVisible(alwaysVisible); } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 62630c8909d3..d27479299efa 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -163,6 +163,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration GRAMMATICAL_GENDER_FEMININE, GRAMMATICAL_GENDER_MASCULINE, }) + @Retention(RetentionPolicy.SOURCE) public @interface GrammaticalGender {} /** @@ -698,6 +699,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration ORIENTATION_LANDSCAPE, ORIENTATION_SQUARE }) + @Retention(RetentionPolicy.SOURCE) public @interface Orientation { } diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 33f602bbd40e..c59d3cea0414 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -163,7 +163,7 @@ public final class SQLiteRawStatement implements Closeable { * {@link IllegalStateException} if a transaction is not in progress. Clients should call * {@link SQLiteDatabase.createRawStatement} to create a new instance. */ - SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException { + SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) { mThread = Thread.currentThread(); mDatabase = db; mSession = mDatabase.getThreadSession(); @@ -245,7 +245,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteDatabaseLockedException if the database is locked or busy. * @throws SQLiteException if a native error occurs. */ - public boolean step() throws SQLiteException { + public boolean step() { throwIfInvalid(); try { int err = nativeStep(mStatement, true); @@ -392,7 +392,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindBlob(int parameterIndex, @NonNull byte[] value) throws SQLiteException { + public void bindBlob(int parameterIndex, @NonNull byte[] value) { Objects.requireNonNull(value); throwIfInvalid(); try { @@ -418,8 +418,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length) - throws SQLiteException { + public void bindBlob(int parameterIndex, @NonNull byte[] value, int offset, int length) { Objects.requireNonNull(value); throwIfInvalid(); throwIfInvalidBounds(value.length, offset, length); @@ -442,7 +441,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindDouble(int parameterIndex, double value) throws SQLiteException { + public void bindDouble(int parameterIndex, double value) { throwIfInvalid(); try { nativeBindDouble(mStatement, parameterIndex, value); @@ -462,7 +461,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindInt(int parameterIndex, int value) throws SQLiteException { + public void bindInt(int parameterIndex, int value) { throwIfInvalid(); try { nativeBindInt(mStatement, parameterIndex, value); @@ -482,7 +481,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindLong(int parameterIndex, long value) throws SQLiteException { + public void bindLong(int parameterIndex, long value) { throwIfInvalid(); try { nativeBindLong(mStatement, parameterIndex, value); @@ -502,7 +501,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindNull(int parameterIndex) throws SQLiteException { + public void bindNull(int parameterIndex) { throwIfInvalid(); try { nativeBindNull(mStatement, parameterIndex); @@ -523,7 +522,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the parameter is out of range. * @throws SQLiteException if a native error occurs. */ - public void bindText(int parameterIndex, @NonNull String value) throws SQLiteException { + public void bindText(int parameterIndex, @NonNull String value) { Objects.requireNonNull(value); throwIfInvalid(); try { @@ -562,7 +561,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @SQLiteDataType - public int getColumnType(int columnIndex) throws SQLiteException { + public int getColumnType(int columnIndex) { throwIfInvalid(); try { return nativeColumnType(mStatement, columnIndex); @@ -584,7 +583,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteOutOfMemoryException if the database cannot allocate memory for the name. */ @NonNull - public String getColumnName(int columnIndex) throws SQLiteException { + public String getColumnName(int columnIndex) { throwIfInvalid(); try { return nativeColumnName(mStatement, columnIndex); @@ -609,7 +608,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public int getColumnLength(int columnIndex) throws SQLiteException { + public int getColumnLength(int columnIndex) { throwIfInvalid(); try { return nativeColumnBytes(mStatement, columnIndex); @@ -635,7 +634,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @Nullable - public byte[] getColumnBlob(int columnIndex) throws SQLiteException { + public byte[] getColumnBlob(int columnIndex) { throwIfInvalid(); try { return nativeColumnBlob(mStatement, columnIndex); @@ -668,8 +667,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ public int readColumnBlob(int columnIndex, @NonNull byte[] buffer, int offset, - int length, int srcOffset) - throws SQLiteException { + int length, int srcOffset) { Objects.requireNonNull(buffer); throwIfInvalid(); throwIfInvalidBounds(buffer.length, offset, length); @@ -695,7 +693,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public double getColumnDouble(int columnIndex) throws SQLiteException { + public double getColumnDouble(int columnIndex) { throwIfInvalid(); try { return nativeColumnDouble(mStatement, columnIndex); @@ -719,7 +717,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public int getColumnInt(int columnIndex) throws SQLiteException { + public int getColumnInt(int columnIndex) { throwIfInvalid(); try { return nativeColumnInt(mStatement, columnIndex); @@ -743,7 +741,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteBindOrColumnIndexOutOfRangeException if the column is out of range. * @throws SQLiteException if a native error occurs. */ - public long getColumnLong(int columnIndex) throws SQLiteException { + public long getColumnLong(int columnIndex) { throwIfInvalid(); try { return nativeColumnLong(mStatement, columnIndex); @@ -768,7 +766,7 @@ public final class SQLiteRawStatement implements Closeable { * @throws SQLiteException if a native error occurs. */ @NonNull - public String getColumnText(int columnIndex) throws SQLiteException { + public String getColumnText(int columnIndex) { throwIfInvalid(); try { return nativeColumnText(mStatement, columnIndex); diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 90bbca8336e1..f82f79eba8c9 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -131,6 +131,7 @@ public class BiometricManager { BIOMETRIC_CONVENIENCE, DEVICE_CREDENTIAL, }) + @Retention(RetentionPolicy.SOURCE) @interface Types {} /** diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index ea951a55bfca..a978bd8a3bdf 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -858,6 +858,7 @@ public final class CameraExtensionCharacteristics { switch(format) { case ImageFormat.YUV_420_888: case ImageFormat.JPEG: + case ImageFormat.JPEG_R: break; default: throw new IllegalArgumentException("Unsupported format: " + format); diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index aca6d0646a9b..5d0697806512 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -3402,8 +3402,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -3464,6 +3464,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1c66f82767e6..0d204f3ececa 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4103,8 +4103,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Long>("android.sensor.exposureTime", long.class); /** - * <p>Duration from start of frame exposure to - * start of next frame exposure.</p> + * <p>Duration from start of frame readout to + * start of next frame readout.</p> * <p>The maximum frame rate that can be supported by a camera subsystem is * a function of many factors:</p> * <ul> @@ -4165,6 +4165,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>For more details about stalling, see {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputStallDuration }.</p> * <p>This control is only effective if {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} or {@link CaptureRequest#CONTROL_MODE android.control.mode} is set to * OFF; otherwise the auto-exposure algorithm will override this value.</p> + * <p><em>Note:</em> Prior to Android 13, this field was described as measuring the duration from + * start of frame exposure to start of next frame exposure, which doesn't reflect the + * definition from sensor manufacturer. A mobile sensor defines the frame duration as + * intervals between sensor readouts.</p> * <p><b>Units</b>: Nanoseconds</p> * <p><b>Range of valid values:</b><br> * See {@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}, {@link android.hardware.camera2.params.StreamConfigurationMap }. diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java index f4fc472accbe..a8066aa74f95 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -47,7 +47,8 @@ public final class CameraExtensionUtils { public static final int[] SUPPORTED_CAPTURE_OUTPUT_FORMATS = { CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT, - ImageFormat.JPEG + ImageFormat.JPEG, + ImageFormat.JPEG_R }; public static class SurfaceInfo { @@ -92,6 +93,10 @@ public final class CameraExtensionUtils { (dataspace == StreamConfigurationMap.HAL_DATASPACE_V0_JFIF)) { surfaceInfo.mFormat = ImageFormat.JPEG; return surfaceInfo; + } else if ((nativeFormat == StreamConfigurationMap.HAL_PIXEL_FORMAT_BLOB) + && (dataspace == StreamConfigurationMap.HAL_DATASPACE_JPEG_R)) { + surfaceInfo.mFormat = ImageFormat.JPEG_R; + return surfaceInfo; } return surfaceInfo; diff --git a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java index 8304796f636a..cf496d2ec88e 100644 --- a/core/java/android/hardware/camera2/impl/FrameNumberTracker.java +++ b/core/java/android/hardware/camera2/impl/FrameNumberTracker.java @@ -90,6 +90,22 @@ public class FrameNumberTracker { break; } } + + if (!removeError) { + // Check for the case where we might have an error after a frame number gap + // caused by other types of capture requests + int otherType1 = (requestType + 1) % CaptureRequest.REQUEST_TYPE_COUNT; + int otherType2 = (requestType + 2) % CaptureRequest.REQUEST_TYPE_COUNT; + if (mPendingFrameNumbersWithOtherType[otherType1].isEmpty() && + mPendingFrameNumbersWithOtherType[otherType2].isEmpty()) { + long errorGapNumber = Math.max(mCompletedFrameNumber[otherType1], + mCompletedFrameNumber[otherType2]) + 1; + if ((errorGapNumber > mCompletedFrameNumber[requestType] + 1) && + (errorGapNumber == errorFrameNumber)) { + removeError = true; + } + } + } } if (removeError) { mCompletedFrameNumber[requestType] = errorFrameNumber; diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index b0b7a416c019..440585c845f8 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -159,6 +159,7 @@ public final class HdmiControlManager { RESULT_INCORRECT_MODE, RESULT_COMMUNICATION_FAILED, }) + @Retention(RetentionPolicy.SOURCE) public @interface ControlCallbackResult {} /** Control operation is successfully handled by the framework. */ @@ -1135,6 +1136,7 @@ public final class HdmiControlManager { CEC_SETTING_NAME_QUERY_SAD_MAX, SETTING_NAME_EARC_ENABLED, }) + @Retention(RetentionPolicy.SOURCE) public @interface SettingName {} /** @@ -1157,6 +1159,7 @@ public final class HdmiControlManager { CEC_SETTING_NAME_QUERY_SAD_WMAPRO, CEC_SETTING_NAME_QUERY_SAD_MAX, }) + @Retention(RetentionPolicy.SOURCE) public @interface CecSettingSad {} // True if we have a logical device of type playback hosted in the system. diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java index 8133472961a0..7f2d8a026a2f 100644 --- a/core/java/android/hardware/input/VirtualDpad.java +++ b/core/java/android/hardware/input/VirtualDpad.java @@ -52,8 +52,8 @@ public class VirtualDpad extends VirtualInputDevice { KeyEvent.KEYCODE_DPAD_CENTER))); /** @hide */ - public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) { - super(virtualDevice, token); + public VirtualDpad(VirtualDpadConfig config, IVirtualDevice virtualDevice, IBinder token) { + super(config, virtualDevice, token); } /** diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java index 772ba8e36c5e..931e1ff10505 100644 --- a/core/java/android/hardware/input/VirtualInputDevice.java +++ b/core/java/android/hardware/input/VirtualInputDevice.java @@ -42,9 +42,12 @@ abstract class VirtualInputDevice implements Closeable { */ protected final IBinder mToken; + protected final VirtualInputDeviceConfig mConfig; + /** @hide */ - VirtualInputDevice( + VirtualInputDevice(VirtualInputDeviceConfig config, IVirtualDevice virtualDevice, IBinder token) { + mConfig = config; mVirtualDevice = virtualDevice; mToken = token; } @@ -70,4 +73,9 @@ abstract class VirtualInputDevice implements Closeable { throw e.rethrowFromSystemServer(); } } + + @Override + public String toString() { + return mConfig.toString(); + } } diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java index d3dacc90d1b4..a8caa58ada01 100644 --- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java +++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java @@ -91,6 +91,22 @@ public abstract class VirtualInputDeviceConfig { dest.writeString8(mInputDeviceName); } + @Override + public String toString() { + return getClass().getName() + "( " + + " name=" + mInputDeviceName + + " vendorId=" + mVendorId + + " productId=" + mProductId + + " associatedDisplayId=" + mAssociatedDisplayId + + additionalFieldsToString() + ")"; + } + + /** @hide */ + @NonNull + String additionalFieldsToString() { + return ""; + } + /** * A builder for {@link VirtualInputDeviceConfig} * diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java index dc47f0820582..4d89508c0be6 100644 --- a/core/java/android/hardware/input/VirtualKeyEvent.java +++ b/core/java/android/hardware/input/VirtualKeyEvent.java @@ -205,6 +205,14 @@ public final class VirtualKeyEvent implements Parcelable { return 0; } + @Override + public String toString() { + return "VirtualKeyEvent(" + + " action=" + KeyEvent.actionToString(mAction) + + " keyCode=" + KeyEvent.keyCodeToString(mKeyCode) + + " eventTime(ns)=" + mEventTimeNanos; + } + /** * Returns the key code associated with this event. */ diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java index e569dbf6b6b6..c90f8932a89d 100644 --- a/core/java/android/hardware/input/VirtualKeyboard.java +++ b/core/java/android/hardware/input/VirtualKeyboard.java @@ -39,8 +39,9 @@ public class VirtualKeyboard extends VirtualInputDevice { private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER; /** @hide */ - public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) { - super(virtualDevice, token); + public VirtualKeyboard(VirtualKeyboardConfig config, + IVirtualDevice virtualDevice, IBinder token) { + super(config, virtualDevice, token); } /** diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java index 6d03065214ca..96a1a36ff361 100644 --- a/core/java/android/hardware/input/VirtualKeyboardConfig.java +++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java @@ -99,6 +99,12 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem dest.writeString8(mLayoutType); } + @Override + @NonNull + String additionalFieldsToString() { + return " languageTag=" + mLanguageTag + " layoutType=" + mLayoutType; + } + /** * Builder for creating a {@link VirtualKeyboardConfig}. */ diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 7eba2b8bfdf0..51f3f69eb78e 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -38,8 +38,8 @@ import android.view.MotionEvent; public class VirtualMouse extends VirtualInputDevice { /** @hide */ - public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) { - super(virtualDevice, token); + public VirtualMouse(VirtualMouseConfig config, IVirtualDevice virtualDevice, IBinder token) { + super(config, virtualDevice, token); } /** diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java index dfdd3b45923b..fc42b1581091 100644 --- a/core/java/android/hardware/input/VirtualMouseButtonEvent.java +++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java @@ -110,6 +110,14 @@ public final class VirtualMouseButtonEvent implements Parcelable { return 0; } + @Override + public String toString() { + return "VirtualMouseButtonEvent(" + + " action=" + MotionEvent.actionToString(mAction) + + " button=" + MotionEvent.buttonStateToString(mButtonCode) + + " eventTime(ns)=" + mEventTimeNanos; + } + /** * Returns the button code associated with this event. */ diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java index e6ad118a2ee0..2a42cfc57c77 100644 --- a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java +++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java @@ -61,6 +61,14 @@ public final class VirtualMouseRelativeEvent implements Parcelable { return 0; } + @Override + public String toString() { + return "VirtualMouseRelativeEvent(" + + " x=" + mRelativeX + + " y=" + mRelativeY + + " eventTime(ns)=" + mEventTimeNanos; + } + /** * Returns the relative x-axis movement, in pixels. */ diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java index 4d0a1576b6ee..c89c188a443d 100644 --- a/core/java/android/hardware/input/VirtualMouseScrollEvent.java +++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java @@ -65,6 +65,14 @@ public final class VirtualMouseScrollEvent implements Parcelable { return 0; } + @Override + public String toString() { + return "VirtualMouseScrollEvent(" + + " x=" + mXAxisMovement + + " y=" + mYAxisMovement + + " eventTime(ns)=" + mEventTimeNanos; + } + /** * Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values * indicate scrolling upward; negative values, downward. diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java index 2854034cd127..61d72e2fd554 100644 --- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java +++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java @@ -40,8 +40,9 @@ import android.os.RemoteException; public class VirtualNavigationTouchpad extends VirtualInputDevice { /** @hide */ - public VirtualNavigationTouchpad(IVirtualDevice virtualDevice, IBinder token) { - super(virtualDevice, token); + public VirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config, + IVirtualDevice virtualDevice, IBinder token) { + super(config, virtualDevice, token); } /** diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java index 8935efa96720..75f7b3e3035e 100644 --- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java +++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java @@ -70,6 +70,12 @@ public final class VirtualNavigationTouchpadConfig extends VirtualInputDeviceCon dest.writeInt(mWidth); } + @Override + @NonNull + String additionalFieldsToString() { + return " width=" + mWidth + " height=" + mHeight; + } + @NonNull public static final Creator<VirtualNavigationTouchpadConfig> CREATOR = new Creator<VirtualNavigationTouchpadConfig>() { diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java index 2695a799d610..7936dfef7748 100644 --- a/core/java/android/hardware/input/VirtualTouchEvent.java +++ b/core/java/android/hardware/input/VirtualTouchEvent.java @@ -138,6 +138,19 @@ public final class VirtualTouchEvent implements Parcelable { return 0; } + @Override + public String toString() { + return "VirtualTouchEvent(" + + " pointerId=" + mPointerId + + " toolType=" + MotionEvent.toolTypeToString(mToolType) + + " action=" + MotionEvent.actionToString(mAction) + + " x=" + mX + + " y=" + mY + + " pressure=" + mPressure + + " majorAxisSize=" + mMajorAxisSize + + " eventTime(ns)=" + mEventTimeNanos; + } + /** * Returns the pointer id associated with this event. */ diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java index 0d07753b9b60..4ac439e0eff1 100644 --- a/core/java/android/hardware/input/VirtualTouchscreen.java +++ b/core/java/android/hardware/input/VirtualTouchscreen.java @@ -34,8 +34,9 @@ import android.os.RemoteException; @SystemApi public class VirtualTouchscreen extends VirtualInputDevice { /** @hide */ - public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) { - super(virtualDevice, token); + public VirtualTouchscreen(VirtualTouchscreenConfig config, + IVirtualDevice virtualDevice, IBinder token) { + super(config, virtualDevice, token); } /** diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java index aac341ccb5ed..63084592a2e8 100644 --- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java +++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java @@ -69,6 +69,12 @@ public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig imp dest.writeInt(mHeight); } + @Override + @NonNull + String additionalFieldsToString() { + return " width=" + mWidth + " height=" + mHeight; + } + @NonNull public static final Creator<VirtualTouchscreenConfig> CREATOR = new Creator<VirtualTouchscreenConfig>() { diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 01ce7b9c0731..481ec7207c8a 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -15,6 +15,8 @@ */ package android.hardware.location; +import static java.util.Objects.requireNonNull; + import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; @@ -1111,12 +1113,12 @@ public final class ContextHubManager { } }; - /** @throws ServiceNotFoundException - * @hide */ - public ContextHubManager(Context context, Looper mainLooper) throws ServiceNotFoundException { + /** @hide */ + public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) { + requireNonNull(service, "service cannot be null"); + requireNonNull(mainLooper, "mainLooper cannot be null"); + mService = service; mMainLooper = mainLooper; - mService = IContextHubService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.CONTEXTHUB_SERVICE)); try { mService.registerCallback(mClientCallback); } catch (RemoteException e) { diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 889d3df0941a..81a023451f16 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -52,6 +52,8 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -738,6 +740,7 @@ public class UsbManager { FUNCTION_NCM, FUNCTION_UVC, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbFunctionMode {} /** @hide */ @@ -748,6 +751,7 @@ public class UsbManager { GADGET_HAL_V1_2, GADGET_HAL_V2_0, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbGadgetHalVersion {} /** @hide */ @@ -759,6 +763,7 @@ public class UsbManager { USB_HAL_V1_3, USB_HAL_V2_0, }) + @Retention(RetentionPolicy.SOURCE) public @interface UsbHalVersion {} /** diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig index 5c5083a691be..63ae28f6ff9d 100644 --- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig +++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig @@ -13,3 +13,10 @@ flag { description: "Flag incompatible charging on COMPLIANCE_WARNING_INPUT_POWER_LIMITED instead of COMPLIANCE_WARNING_OTHER when enabled" bug: "308700954" } + +flag { + name: "enable_report_usb_data_compliance_warning" + namespace: "system_sw_usb" + description: "Enable reporting USB data compliance warnings from HAL when set" + bug: "296119135" +} diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 0c95c2ec7a7a..f6beec179d57 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -84,4 +84,6 @@ interface INfcAdapter boolean isReaderOptionSupported(); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean enableReaderOption(boolean enable); + boolean isObserveModeSupported(); + boolean setObserveMode(boolean enabled); } diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index c7b3b2c03f65..191385a3c13d 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -30,6 +30,7 @@ interface INfcCardEmulation boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); boolean setDefaultForNextTap(int userHandle, in ComponentName service); + boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index c89759553810..98a980f5e7f8 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1081,6 +1081,61 @@ public final class NfcAdapter { } } + + /** + * Returns whether the device supports observer mode or not. When observe + * mode is enabled, the NFC hardware will listen for NFC readers, but not + * respond to them. When observe mode is disabled, the NFC hardware will + * resoond to the reader and proceed with the transaction. + * @return true if the mode is supported, false otherwise. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean isObserveModeSupported() { + try { + return sService.isObserveModeSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Disables observe mode to allow the transaction to proceed. See + * {@link #isObserveModeSupported()} for a description of observe mode and + * use {@link #disallowTransaction()} to enable observe mode and block + * transactions again. + * + * @return boolean indicating success or failure. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean allowTransaction() { + try { + return sService.setObserveMode(false); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Signals that the transaction has completed and observe mode may be + * reenabled. See {@link #isObserveModeSupported()} for a description of + * observe mode and use {@link #allowTransaction()} to disable observe + * mode and allow transactions to proceed. + * + * @return boolean indicating success or failure. + */ + + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean disallowTransaction() { + try { + return sService.setObserveMode(true); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + /** * Resumes default polling for the current device state if polling is paused. Calling * this while polling is not paused is a no-op. diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index d048b595ad1e..58b6179691e9 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -328,6 +328,24 @@ public final class CardEmulation { return SELECTION_MODE_ASK_IF_CONFLICT; } } + /** + * Sets whether the system should default to observe mode or not when + * the service is in the foreground or the default payment service. + * + * @param service The component name of the service + * @param enable Whether the servic should default to observe mode or not + * @return whether the change was successful. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) { + try { + return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(), + service, enable); + } catch (RemoteException e) { + Log.e(TAG, "Failed to reach CardEmulationService."); + } + return false; + } /** * Registers a list of AIDs for a specific category for the diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index 55d0e73780a2..7cd2533a7dbf 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -16,11 +16,14 @@ package android.nfc.cardemulation; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -29,6 +32,9 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.List; + /** * <p>HostApduService is a convenience {@link Service} class that can be * extended to emulate an NFC card inside an Android @@ -230,9 +236,99 @@ public abstract class HostApduService extends Service { /** * @hide */ + public static final int MSG_POLLING_LOOP = 4; + + /** + * @hide + */ public static final String KEY_DATA = "data"; /** + * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of + * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + + /** + * POLLING_LOOP_TYPE_A is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-A. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_A = 'A'; + + /** + * POLLING_LOOP_TYPE_B is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-B. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_B = 'B'; + + /** + * POLLING_LOOP_TYPE_F is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-F. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_F = 'F'; + + /** + * POLLING_LOOP_TYPE_ON is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns on. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_ON = 'O'; + + /** + * POLLING_LOOP_TYPE_OFF is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns off. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_OFF = 'X'; + + /** + * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop frame isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U'; + + /** + * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + + /** + * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + + /** + * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + + /** + * @hide + */ + public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY = + "android.nfc.cardemulation.POLLING_FRAMES"; + + /** * Messenger interface to NfcService for sending responses. * Only accessed on main thread by the message handler. * @@ -255,6 +351,7 @@ public abstract class HostApduService extends Service { byte[] apdu = dataBundle.getByteArray(KEY_DATA); if (apdu != null) { + HostApduService has = HostApduService.this; byte[] responseApdu = processCommandApdu(apdu, null); if (responseApdu != null) { if (mNfcService == null) { @@ -306,6 +403,12 @@ public abstract class HostApduService extends Service { Log.e(TAG, "RemoteException calling into NfcService."); } break; + case MSG_POLLING_LOOP: + ArrayList<Bundle> frames = + msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY, + Bundle.class); + processPollingFrames(frames); + break; default: super.handleMessage(msg); } @@ -366,6 +469,21 @@ public abstract class HostApduService extends Service { } } + /** + * This method is called when a polling frame has been received from a + * remote device. If the device is in observe mode, the service should + * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed + * with the transaction. If the device is not in observe mode, the service + * can use this polling frame information to determine how to proceed if it + * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The + * service must override this method inorder to receive polling frames, + * otherwise the base implementation drops the frame. + * + * @param frame A description of the polling frame. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public void processPollingFrames(@NonNull List<Bundle> frame) { + } /** * <p>This method will be called when a command APDU has been received diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index cd50ace036de..17e042761dbe 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -20,3 +20,31 @@ flag { description: "Flag for NFC user restriction" bug: "291187960" } + +flag { + name: "nfc_observe_mode" + namespace: "nfc" + description: "Enable NFC Observe Mode" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications" + bug: "294217286" +} + +flag { + name: "nfc_observe_mode_st_shim" + namespace: "nfc" + description: "Enable NFC Observe Mode ST shim" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop_st_shim" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications ST shim" + bug: "294217286" +} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 995622004fa6..0ccc485a34f4 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -30,9 +30,11 @@ import com.android.internal.os.BinderCallHeavyHitterWatcher; import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener; import com.android.internal.os.BinderInternal; import com.android.internal.os.BinderInternal.CallSession; +import com.android.internal.os.SomeArgs; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; +import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.CriticalNative; @@ -46,6 +48,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Modifier; import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.Supplier; /** * Base class for a remotable object, the core part of a lightweight @@ -289,6 +292,33 @@ public class Binder implements IBinder { sWarnOnBlockingOnCurrentThread.set(sWarnOnBlocking); } + private static ThreadLocal<SomeArgs> sIdentity$ravenwood; + + @android.ravenwood.annotation.RavenwoodKeepWholeClass + private static class IdentitySupplier implements Supplier<SomeArgs> { + @Override + public SomeArgs get() { + final SomeArgs args = SomeArgs.obtain(); + // Match IPCThreadState behavior + args.arg1 = Boolean.FALSE; + args.argi1 = android.os.Process.myUid(); + args.argi2 = android.os.Process.myPid(); + return args; + } + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void init$ravenwood() { + sIdentity$ravenwood = ThreadLocal.withInitial(new IdentitySupplier()); + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void reset$ravenwood() { + sIdentity$ravenwood = null; + } + /** * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null. */ @@ -312,8 +342,14 @@ public class Binder implements IBinder { * Warning: oneway transactions do not receive PID. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native int getCallingPid(); + /** @hide */ + public static final int getCallingPid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2; + } + /** * Return the Linux UID assigned to the process that sent you the * current transaction that is being processed. This UID can be used with @@ -322,8 +358,14 @@ public class Binder implements IBinder { * incoming transaction, then its own UID is returned. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native int getCallingUid(); + /** @hide */ + public static final int getCallingUid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1; + } + /** * Returns {@code true} if the current thread is currently executing an * incoming transaction. @@ -331,6 +373,7 @@ public class Binder implements IBinder { * @hide */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native boolean isDirectlyHandlingTransactionNative(); /** @hide */ @@ -344,6 +387,7 @@ public class Binder implements IBinder { /** * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static final boolean isDirectlyHandlingTransaction() { return sIsHandlingBinderTransaction || isDirectlyHandlingTransactionNative(); } @@ -363,8 +407,15 @@ public class Binder implements IBinder { * @hide */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace private static native boolean hasExplicitIdentity(); + /** @hide */ + private static boolean hasExplicitIdentity$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().arg1 + == Boolean.TRUE; + } + /** * Return the Linux UID assigned to the process that sent the transaction * currently being processed. @@ -373,6 +424,7 @@ public class Binder implements IBinder { * executing an incoming transaction and the calling identity has not been * explicitly set with {@link #clearCallingIdentity()} */ + @android.ravenwood.annotation.RavenwoodKeep public static final int getCallingUidOrThrow() { if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) { throw new IllegalStateException( @@ -434,8 +486,26 @@ public class Binder implements IBinder { * @see #restoreCallingIdentity(long) */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native long clearCallingIdentity(); + /** @hide */ + public static final long clearCallingIdentity$ravenwood() { + final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule( + sIdentity$ravenwood).get(); + long res = ((long) args.argi1 << 32) | args.argi2; + if (args.arg1 == Boolean.TRUE) { + res |= (0x1 << 30); + } else { + res &= ~(0x1 << 30); + } + // Match IPCThreadState behavior + args.arg1 = Boolean.TRUE; + args.argi1 = android.os.Process.myUid(); + args.argi2 = android.os.Process.myPid(); + return res; + } + /** * Restore the identity of the incoming IPC on the current thread * back to a previously identity that was returned by {@link @@ -447,8 +517,18 @@ public class Binder implements IBinder { * @see #clearCallingIdentity */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static final native void restoreCallingIdentity(long token); + /** @hide */ + public static final void restoreCallingIdentity$ravenwood(long token) { + final SomeArgs args = Preconditions.requireNonNullViaRavenwoodRule( + sIdentity$ravenwood).get(); + args.arg1 = ((token & (0x1 << 30)) != 0) ? Boolean.TRUE : Boolean.FALSE; + args.argi1 = (int) (token >> 32); + args.argi2 = (int) (token & ~(0x1 << 30)); + } + /** * Convenience method for running the provided action enclosed in * {@link #clearCallingIdentity}/{@link #restoreCallingIdentity}. @@ -644,8 +724,14 @@ public class Binder implements IBinder { * in order to prevent the process from holding on to objects longer than * it needs to. */ + @android.ravenwood.annotation.RavenwoodReplace public static final native void flushPendingCommands(); + /** @hide */ + public static final void flushPendingCommands$ravenwood() { + // Ravenwood doesn't support IPC; ignored + } + /** * Add the calling thread to the IPC thread pool. This function does * not return until the current process is exiting. @@ -703,6 +789,7 @@ public class Binder implements IBinder { * <p>If you're creating a Binder token (a Binder object without an attached interface), * you should use {@link #Binder(String)} instead. */ + @android.ravenwood.annotation.RavenwoodKeep public Binder() { this(null); } @@ -719,6 +806,7 @@ public class Binder implements IBinder { * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to * help identify them. */ + @android.ravenwood.annotation.RavenwoodKeep public Binder(@Nullable String descriptor) { mObject = getNativeBBinderHolder(); if (mObject != 0L) { @@ -742,6 +830,7 @@ public class Binder implements IBinder { * will be implemented for you to return the given owner IInterface when * the corresponding descriptor is requested. */ + @android.ravenwood.annotation.RavenwoodKeep public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) { mOwner = owner; mDescriptor = descriptor; @@ -750,6 +839,7 @@ public class Binder implements IBinder { /** * Default implementation returns an empty interface name. */ + @android.ravenwood.annotation.RavenwoodKeep public @Nullable String getInterfaceDescriptor() { return mDescriptor; } @@ -758,6 +848,7 @@ public class Binder implements IBinder { * Default implementation always returns true -- if you got here, * the object is alive. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean pingBinder() { return true; } @@ -768,6 +859,7 @@ public class Binder implements IBinder { * Note that if you're calling on a local binder, this always returns true * because your process is alive if you're calling it. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean isBinderAlive() { return true; } @@ -777,6 +869,7 @@ public class Binder implements IBinder { * to return the associated {@link IInterface} if it matches the requested * descriptor. */ + @android.ravenwood.annotation.RavenwoodKeep public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; @@ -1250,12 +1343,14 @@ public class Binder implements IBinder { /** * Local implementation is a no-op. */ + @android.ravenwood.annotation.RavenwoodKeep public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { } /** * Local implementation is a no-op. */ + @android.ravenwood.annotation.RavenwoodKeep public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { return true; } @@ -1283,6 +1378,7 @@ public class Binder implements IBinder { } } + @android.ravenwood.annotation.RavenwoodReplace private static native long getNativeBBinderHolder(); private static long getNativeBBinderHolder$ravenwood() { diff --git a/core/java/android/os/DeadObjectException.java b/core/java/android/os/DeadObjectException.java index e06b0f9f4bc2..65ed618fb075 100644 --- a/core/java/android/os/DeadObjectException.java +++ b/core/java/android/os/DeadObjectException.java @@ -19,7 +19,8 @@ import android.os.RemoteException; /** * The object you are calling has died, because its hosting process - * no longer exists. + * no longer exists. This is also thrown for low-level binder + * errors. */ public class DeadObjectException extends RemoteException { public DeadObjectException() { diff --git a/core/java/android/os/DeadSystemRuntimeException.java b/core/java/android/os/DeadSystemRuntimeException.java index 1e869249eb9d..82b1ad8a8630 100644 --- a/core/java/android/os/DeadSystemRuntimeException.java +++ b/core/java/android/os/DeadSystemRuntimeException.java @@ -18,9 +18,10 @@ package android.os; /** * Exception thrown when a call into system_server resulted in a - * DeadObjectException, meaning that the system_server has died. There's - * nothing apps can do at this point - the system will automatically restart - - * so there's no point in catching this. + * DeadObjectException, meaning that the system_server has died or + * experienced a low-level binder error. There's * nothing apps can + * do at this point - the system will automatically restart - so + * there's no point in catching this. * * @hide */ diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index 90e4b17250d8..91c2965c2505 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -194,6 +194,7 @@ public interface IBinder { * Limit that should be placed on IPC sizes, in bytes, to keep them safely under the transaction * buffer limit. */ + @android.ravenwood.annotation.RavenwoodKeep static int getSuggestedMaxIpcSizeBytes() { return MAX_IPC_SIZE; } diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl index 6b43e73d10e7..fe85da26e610 100644 --- a/core/java/android/os/IHintSession.aidl +++ b/core/java/android/os/IHintSession.aidl @@ -17,6 +17,8 @@ package android.os; +import android.os.WorkDuration; + /** {@hide} */ oneway interface IHintSession { void updateTargetWorkDuration(long targetDurationNanos); @@ -24,4 +26,5 @@ oneway interface IHintSession { void close(); void sendHint(int hint); void setMode(int mode, boolean enabled); + void reportActualWorkDuration2(in WorkDuration[] workDurations); } diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index f30dd20d7087..0f2756914fa6 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -33,13 +33,13 @@ interface IVibratorManagerService { boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener); boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in CombinedVibration vibration, in VibrationAttributes attributes); - void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration, + void vibrate(int uid, int deviceId, String opPkg, in CombinedVibration vibration, in VibrationAttributes attributes, String reason, IBinder token); void cancelVibrate(int usageFilter, IBinder token); // Async oneway APIs. // There is no order guarantee with respect to the two-way APIs above like // vibrate/isVibrating/cancel. - oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant, + oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token); } diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java index 11084b88fad1..e0059105c21f 100644 --- a/core/java/android/os/PerformanceHintManager.java +++ b/core/java/android/os/PerformanceHintManager.java @@ -103,7 +103,7 @@ public final class PerformanceHintManager { * Any call in this class will change its internal data, so you must do your own thread * safety to protect from racing. * - * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + * All timings should be in {@link SystemClock#uptimeNanos()}. */ public static class Session implements Closeable { private long mNativeSessionPtr; @@ -269,6 +269,40 @@ public final class PerformanceHintManager { public @Nullable int[] getThreadIds() { return nativeGetThreadIds(mNativeSessionPtr); } + + /** + * Reports the work duration for the last cycle of work. + * + * The system will attempt to adjust the core placement of the threads within the thread + * group and/or the frequency of the core on which they are run to bring the actual duration + * close to the target duration. + * + * @param workDuration the work duration of each component. + * @throws IllegalArgumentException if work period start timestamp is not positive, or + * actual total duration is not positive, or actual CPU duration is not positive, + * or actual GPU duration is negative. + */ + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) + public void reportActualWorkDuration(@NonNull WorkDuration workDuration) { + if (workDuration.mWorkPeriodStartTimestampNanos <= 0) { + throw new IllegalArgumentException( + "the work period start timestamp should be positive."); + } + if (workDuration.mActualTotalDurationNanos <= 0) { + throw new IllegalArgumentException("the actual total duration should be positive."); + } + if (workDuration.mActualCpuDurationNanos <= 0) { + throw new IllegalArgumentException("the actual CPU duration should be positive."); + } + if (workDuration.mActualGpuDurationNanos < 0) { + throw new IllegalArgumentException( + "the actual GPU duration should be non negative."); + } + nativeReportActualWorkDuration(mNativeSessionPtr, + workDuration.mWorkPeriodStartTimestampNanos, + workDuration.mActualTotalDurationNanos, + workDuration.mActualCpuDurationNanos, workDuration.mActualGpuDurationNanos); + } } private static native long nativeAcquireManager(); @@ -285,4 +319,7 @@ public final class PerformanceHintManager { private static native void nativeSetThreads(long nativeSessionPtr, int[] tids); private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr, boolean enabled); + private static native void nativeReportActualWorkDuration(long nativeSessionPtr, + long workPeriodStartTimestampNanos, long actualTotalDurationNanos, + long actualCpuDurationNanos, long actualGpuDurationNanos); } diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 677143afd4fb..daec1721977b 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -34,6 +34,9 @@ import android.system.StructPollfd; import android.util.Pair; import android.webkit.WebViewZygote; +import com.android.internal.os.SomeArgs; +import com.android.internal.util.Preconditions; + import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -833,14 +836,37 @@ public class Process { return VMRuntime.getRuntime().is64Bit(); } + private static SomeArgs sIdentity$ravenwood; + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void init$ravenwood(int uid, int pid) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = uid; + args.argi2 = pid; + sIdentity$ravenwood = args; + } + + /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep + public static void reset$ravenwood() { + sIdentity$ravenwood = null; + } + /** * Returns the identifier of this process, which can be used with * {@link #killProcess} and {@link #sendSignal}. */ + @android.ravenwood.annotation.RavenwoodReplace public static final int myPid() { return Os.getpid(); } + /** @hide */ + public static final int myPid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi2; + } + /** * Returns the identifier of this process' parent. * @hide @@ -864,10 +890,16 @@ public class Process { * app-specific sandbox. It is different from {@link #myUserHandle} in that * a uid identifies a specific app sandbox in a specific user. */ + @android.ravenwood.annotation.RavenwoodReplace public static final int myUid() { return Os.getuid(); } + /** @hide */ + public static final int myUid$ravenwood() { + return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).argi1; + } + /** * Returns this process's user handle. This is the * user the process is running under. It is distinct from diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index d4688f8794a4..f71c2695c677 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -57,6 +57,8 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.GeneralSecurityException; import java.security.PublicKey; import java.security.SignatureException; @@ -166,6 +168,7 @@ public class RecoverySystem { RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED, RESUME_ON_REBOOT_REBOOT_ERROR_SLOT_MISMATCH, RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE}) + @Retention(RetentionPolicy.SOURCE) public @interface ResumeOnRebootRebootErrorCode {} /** diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 831ca86504af..e2a58338230c 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -16,6 +16,7 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.IAlarmManager; import android.app.time.UnixEpochTime; @@ -24,6 +25,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.location.ILocationManager; import android.location.LocationTime; +import android.text.format.DateUtils; import android.util.Slog; import dalvik.annotation.optimization.CriticalNative; @@ -125,6 +127,7 @@ public final class SystemClock { * * @param ms to sleep before returning, in milliseconds of uptime. */ + @android.ravenwood.annotation.RavenwoodKeep public static void sleep(long ms) { long start = uptimeMillis(); @@ -186,17 +189,33 @@ public final class SystemClock { * @return milliseconds of non-sleep uptime since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace native public static long uptimeMillis(); + /** @hide */ + public static long uptimeMillis$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.currentTimeMillis() - (1672556400L * 1_000) + - (DateUtils.WEEK_IN_MILLIS * 1_000); + } + /** * Returns nanoseconds since boot, not counting time spent in deep sleep. * * @return nanoseconds of non-sleep uptime since boot. - * @hide */ + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); + /** @hide */ + public static long uptimeNanos$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() - (1672556400L * 1_000_000_000) + - (DateUtils.WEEK_IN_MILLIS * 1_000_000_000); + } + /** * Return {@link Clock} that starts at system boot, not counting time spent * in deep sleep. @@ -218,8 +237,15 @@ public final class SystemClock { * @return elapsed milliseconds since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace native public static long elapsedRealtime(); + /** @hide */ + public static long elapsedRealtime$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.currentTimeMillis() - (1672556400L * 1_000); + } + /** * Return {@link Clock} that starts at system boot, including time spent in * sleep. @@ -241,8 +267,15 @@ public final class SystemClock { * @return elapsed nanoseconds since boot. */ @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long elapsedRealtimeNanos(); + /** @hide */ + public static long elapsedRealtimeNanos$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() - (1672556400L * 1_000_000_000); + } + /** * Returns milliseconds running in the current thread. * @@ -271,8 +304,15 @@ public final class SystemClock { */ @UnsupportedAppUsage @CriticalNative + @android.ravenwood.annotation.RavenwoodReplace public static native long currentTimeMicro(); + /** @hide */ + public static long currentTimeMicro$ravenwood() { + // Ravenwood booted in Jan 2023, and has been in deep sleep for one week + return System.nanoTime() / 1000L; + } + /** * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized * using a remote network source outside the device. diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index ee90834c15ef..bc85412e851b 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -137,8 +137,8 @@ public class SystemVibratorManager extends VibratorManager { return; } try { - mService.vibrate(uid, mContext.getAssociatedDisplayId(), opPkg, effect, attributes, - reason, mToken); + mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason, + mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to vibrate.", e); } @@ -152,8 +152,8 @@ public class SystemVibratorManager extends VibratorManager { } try { mService.performHapticFeedback( - Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant, - always, reason, mToken); + Process.myUid(), mContext.getDeviceId(), mPackageName, constant, always, reason, + mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index b7e3068a437c..0a8f62fd56d8 100644 --- a/core/java/android/os/UpdateEngine.java +++ b/core/java/android/os/UpdateEngine.java @@ -25,6 +25,9 @@ import android.os.IUpdateEngine; import android.os.IUpdateEngineCallback; import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * UpdateEngine handles calls to the update engine which takes care of A/B OTA * updates. It wraps up the update engine Binder APIs and exposes them as @@ -178,6 +181,7 @@ public class UpdateEngine { ErrorCodeConstants.NOT_ENOUGH_SPACE, ErrorCodeConstants.DEVICE_CORRUPTED, }) + @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} /** diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index d9c6beeb5813..2419a4c391f2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -204,6 +204,8 @@ public class UserManager { * the user in locked state so that a direct boot aware DPC could reset the password. * Should not be used together with * {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED} or an exception will be thrown. + * This flag is currently only allowed for {@link #isManagedProfile() managed profiles}; + * usage on other profiles may result in an Exception. * @hide */ public static final int QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL = 0x2; diff --git a/core/java/android/os/WorkDuration.aidl b/core/java/android/os/WorkDuration.aidl new file mode 100644 index 000000000000..0f61204d72c4 --- /dev/null +++ b/core/java/android/os/WorkDuration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +parcelable WorkDuration cpp_header "android/WorkDuration.h";
\ No newline at end of file diff --git a/core/java/android/os/WorkDuration.java b/core/java/android/os/WorkDuration.java new file mode 100644 index 000000000000..4fdc34fb60d1 --- /dev/null +++ b/core/java/android/os/WorkDuration.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; + +import java.util.Objects; + +/** + * WorkDuration contains the measured time in nano seconds of the workload + * in each component, see + * {@link PerformanceHintManager.Session#reportActualWorkDuration(WorkDuration)}. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ +@FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) +public final class WorkDuration implements Parcelable { + long mWorkPeriodStartTimestampNanos = 0; + long mActualTotalDurationNanos = 0; + long mActualCpuDurationNanos = 0; + long mActualGpuDurationNanos = 0; + long mTimestampNanos = 0; + + public static final @NonNull Creator<WorkDuration> CREATOR = new Creator<>() { + @Override + public WorkDuration createFromParcel(Parcel in) { + return new WorkDuration(in); + } + + @Override + public WorkDuration[] newArray(int size) { + return new WorkDuration[size]; + } + }; + + public WorkDuration() {} + + public WorkDuration(long workPeriodStartTimestampNanos, + long actualTotalDurationNanos, + long actualCpuDurationNanos, + long actualGpuDurationNanos) { + mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; + mActualTotalDurationNanos = actualTotalDurationNanos; + mActualCpuDurationNanos = actualCpuDurationNanos; + mActualGpuDurationNanos = actualGpuDurationNanos; + } + + /** + * @hide + */ + public WorkDuration(long workPeriodStartTimestampNanos, + long actualTotalDurationNanos, + long actualCpuDurationNanos, + long actualGpuDurationNanos, + long timestampNanos) { + mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; + mActualTotalDurationNanos = actualTotalDurationNanos; + mActualCpuDurationNanos = actualCpuDurationNanos; + mActualGpuDurationNanos = actualGpuDurationNanos; + mTimestampNanos = timestampNanos; + } + + WorkDuration(@NonNull Parcel in) { + mWorkPeriodStartTimestampNanos = in.readLong(); + mActualTotalDurationNanos = in.readLong(); + mActualCpuDurationNanos = in.readLong(); + mActualGpuDurationNanos = in.readLong(); + mTimestampNanos = in.readLong(); + } + + /** + * Sets the work period start timestamp in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public void setWorkPeriodStartTimestampNanos(long workPeriodStartTimestampNanos) { + if (workPeriodStartTimestampNanos <= 0) { + throw new IllegalArgumentException( + "the work period start timestamp should be positive."); + } + mWorkPeriodStartTimestampNanos = workPeriodStartTimestampNanos; + } + + /** + * Sets the actual total duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public void setActualTotalDurationNanos(long actualTotalDurationNanos) { + if (actualTotalDurationNanos <= 0) { + throw new IllegalArgumentException("the actual total duration should be positive."); + } + mActualTotalDurationNanos = actualTotalDurationNanos; + } + + /** + * Sets the actual CPU duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public void setActualCpuDurationNanos(long actualCpuDurationNanos) { + if (actualCpuDurationNanos <= 0) { + throw new IllegalArgumentException("the actual CPU duration should be positive."); + } + mActualCpuDurationNanos = actualCpuDurationNanos; + } + + /** + * Sets the actual GPU duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public void setActualGpuDurationNanos(long actualGpuDurationNanos) { + if (actualGpuDurationNanos < 0) { + throw new IllegalArgumentException("the actual GPU duration should be non negative."); + } + mActualGpuDurationNanos = actualGpuDurationNanos; + } + + /** + * Returns the work period start timestamp based in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public long getWorkPeriodStartTimestampNanos() { + return mWorkPeriodStartTimestampNanos; + } + + /** + * Returns the actual total duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public long getActualTotalDurationNanos() { + return mActualTotalDurationNanos; + } + + /** + * Returns the actual CPU duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public long getActualCpuDurationNanos() { + return mActualCpuDurationNanos; + } + + /** + * Returns the actual GPU duration in nanoseconds. + * + * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}. + */ + public long getActualGpuDurationNanos() { + return mActualGpuDurationNanos; + } + + /** + * @hide + */ + public long getTimestampNanos() { + return mTimestampNanos; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mWorkPeriodStartTimestampNanos); + dest.writeLong(mActualTotalDurationNanos); + dest.writeLong(mActualCpuDurationNanos); + dest.writeLong(mActualGpuDurationNanos); + dest.writeLong(mTimestampNanos); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof WorkDuration)) { + return false; + } + WorkDuration workDuration = (WorkDuration) obj; + return workDuration.mTimestampNanos == this.mTimestampNanos + && workDuration.mWorkPeriodStartTimestampNanos == this.mWorkPeriodStartTimestampNanos + && workDuration.mActualTotalDurationNanos == this.mActualTotalDurationNanos + && workDuration.mActualCpuDurationNanos == this.mActualCpuDurationNanos + && workDuration.mActualGpuDurationNanos == this.mActualGpuDurationNanos; + } + + @Override + public int hashCode() { + return Objects.hash(mWorkPeriodStartTimestampNanos, mActualTotalDurationNanos, + mActualCpuDurationNanos, mActualGpuDurationNanos, mTimestampNanos); + } +} diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 940ddf2b2597..0809b3bada20 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -54,4 +54,11 @@ flag { namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." bug: "305067031" -}
\ No newline at end of file +} + +flag { + name: "adpf_gpu_report_actual_work_duration" + namespace: "game" + description: "Guards the ADPF GPU APIs." + bug: "284324521" +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 2d1802ae85e5..6853892348d9 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2133,6 +2133,7 @@ public class StorageManager { MOUNT_MODE_EXTERNAL_PASS_THROUGH, MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE }) + @Retention(RetentionPolicy.SOURCE) /** @hide */ public @interface MountMode {} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 7369740bc43c..fb2ac7112b0a 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -44,6 +44,13 @@ flag { } flag { + name: "enhanced_confirmation_mode_apis" + namespace: "permissions" + description: "enable enhanced confirmation mode apis" + bug: "310220212" +} + +flag { name: "op_enable_mobile_data_by_user" namespace: "permissions" description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c012ff34bfab..33c15d775ff1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12817,15 +12817,6 @@ public final class Settings { "render_shadows_in_compositor"; /** - * If true, submit buffers using blast in ViewRootImpl. - * (0 = false, 1 = true) - * @hide - */ - @Readable - public static final String DEVELOPMENT_USE_BLAST_ADAPTER_VR = - "use_blast_adapter_vr"; - - /** * Path to the WindowManager display settings file. If unset, the default file path will * be used. * @@ -19039,6 +19030,14 @@ public final class Settings { public static final int BATTERY_SAVER_MODE_CUSTOM = 4; /** + Whether 1P apps vote for enabling data during different modes, + i.e. BTM, BBSM + * @hide + */ + @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final String CONNECTIVITY_KEEP_DATA_ON = "wear_connectivity_keep_data_on"; + + /** * The maximum ambient mode duration when an activity is allowed to auto resume. * @hide */ @@ -19464,6 +19463,7 @@ public final class Settings { * * @hide */ + @Readable public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package"; /** @@ -19471,6 +19471,7 @@ public final class Settings { * * @hide */ + @Readable public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package"; /* diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index bcda25a1bf3b..72c436ee6d41 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -17,6 +17,7 @@ package android.provider; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -49,6 +50,7 @@ import android.text.TextUtils; import android.util.Patterns; import com.android.internal.telephony.SmsApplication; +import com.android.internal.telephony.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -3191,8 +3193,8 @@ public final class Telephony { * Sets whether the PDU session brought up by this APN should always be on. * See 3GPP TS 23.501 section 5.6.13 * <P>Type: INTEGER</P> - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String ALWAYS_ON = "always_on"; /** @@ -3302,18 +3304,16 @@ public final class Telephony { * The MTU (maximum transmit unit) size of the mobile interface for IPv4 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V4 = "mtu_v4"; /** * The MTU (maximum transmit unit) size of the mobile interface for IPv6 to which the APN is * connected, in bytes. * <p>Type: INTEGER </p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String MTU_V6 = "mtu_v6"; /** @@ -3335,17 +3335,15 @@ public final class Telephony { /** * {@code true} if this APN visible to the user, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_VISIBLE = "user_visible"; /** * {@code true} if the user allowed to edit this APN, {@code false} otherwise. * <p>Type: INTEGER (boolean)</p> - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public static final String USER_EDITABLE = "user_editable"; /** diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 759953e4aca4..92c516c38dcc 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -309,6 +309,7 @@ public abstract class NotificationListenerService extends Service { REASON_ASSISTANT_CANCEL, REASON_LOCKDOWN, }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationCancelReason{}; /** @@ -320,6 +321,7 @@ public abstract class NotificationListenerService extends Service { FLAG_FILTER_TYPE_SILENT, FLAG_FILTER_TYPE_ONGOING }) + @Retention(RetentionPolicy.SOURCE) public @interface NotificationFilterTypes {} /** * A flag value indicating that this notification listener can see conversation type diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java new file mode 100644 index 000000000000..5b096c641f78 --- /dev/null +++ b/core/java/android/service/notification/ZenDeviceEffects.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.notification; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * Represents the set of device effects (affecting display and device behavior in general) that + * are applied whenever an {@link android.app.AutomaticZenRule} is active. + */ +@FlaggedApi(Flags.FLAG_MODES_API) +public final class ZenDeviceEffects implements Parcelable { + + private final boolean mGrayscale; + private final boolean mSuppressAmbientDisplay; + private final boolean mDimWallpaper; + private final boolean mNightMode; + + private final boolean mDisableAutoBrightness; + private final boolean mDisableTapToWake; + private final boolean mDisableTiltToWake; + private final boolean mDisableTouch; + private final boolean mMinimizeRadioUsage; + private final boolean mMaximizeDoze; + + private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay, + boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness, + boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch, + boolean minimizeRadioUsage, boolean maximizeDoze) { + mGrayscale = grayscale; + mSuppressAmbientDisplay = suppressAmbientDisplay; + mDimWallpaper = dimWallpaper; + mNightMode = nightMode; + mDisableAutoBrightness = disableAutoBrightness; + mDisableTapToWake = disableTapToWake; + mDisableTiltToWake = disableTiltToWake; + mDisableTouch = disableTouch; + mMinimizeRadioUsage = minimizeRadioUsage; + mMaximizeDoze = maximizeDoze; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof final ZenDeviceEffects that)) return false; + if (obj == this) return true; + + return this.mGrayscale == that.mGrayscale + && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay + && this.mDimWallpaper == that.mDimWallpaper + && this.mNightMode == that.mNightMode + && this.mDisableAutoBrightness == that.mDisableAutoBrightness + && this.mDisableTapToWake == that.mDisableTapToWake + && this.mDisableTiltToWake == that.mDisableTiltToWake + && this.mDisableTouch == that.mDisableTouch + && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage + && this.mMaximizeDoze == that.mMaximizeDoze; + } + + @Override + public int hashCode() { + return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode, + mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch, + mMinimizeRadioUsage, mMaximizeDoze); + } + + @Override + public String toString() { + ArrayList<String> effects = new ArrayList<>(10); + if (mGrayscale) effects.add("grayscale"); + if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay"); + if (mDimWallpaper) effects.add("dimWallpaper"); + if (mNightMode) effects.add("nightMode"); + if (mDisableAutoBrightness) effects.add("disableAutoBrightness"); + if (mDisableTapToWake) effects.add("disableTapToWake"); + if (mDisableTiltToWake) effects.add("disableTiltToWake"); + if (mDisableTouch) effects.add("disableTouch"); + if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage"); + if (mMaximizeDoze) effects.add("maximizeDoze"); + return "[" + String.join(", ", effects) + "]"; + } + + /** + * Whether the level of color saturation of the display should be set to minimum, effectively + * switching it to grayscale, while the rule is active. + */ + public boolean shouldDisplayGrayscale() { + return mGrayscale; + } + + /** + * Whether the ambient (always-on) display feature should be disabled while the rule is active. + * This will have no effect if the device doesn't support always-on display or if it's not + * generally enabled. + */ + public boolean shouldSuppressAmbientDisplay() { + return mSuppressAmbientDisplay; + } + + /** Whether the wallpaper should be dimmed while the rule is active. */ + public boolean shouldDimWallpaper() { + return mDimWallpaper; + } + + /** Whether night mode (aka dark theme) should be applied while the rule is active. */ + public boolean shouldUseNightMode() { + return mNightMode; + } + + /** + * Whether the display's automatic brightness adjustment should be disabled while the rule is + * active. + * @hide + */ + public boolean shouldDisableAutoBrightness() { + return mDisableAutoBrightness; + } + + /** + * Whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTapToWake() { + return mDisableTapToWake; + } + + /** + * Whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTiltToWake() { + return mDisableTiltToWake; + } + + /** + * Whether touch interactions should be disabled while the rule is active. + * @hide + */ + public boolean shouldDisableTouch() { + return mDisableTouch; + } + + /** + * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be + * minimized while the rule is active. + * @hide + */ + public boolean shouldMinimizeRadioUsage() { + return mMinimizeRadioUsage; + } + + /** + * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent + * maintenance windows) while the rule is active. + * @hide + */ + public boolean shouldMaximizeDoze() { + return mMaximizeDoze; + } + + /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */ + @NonNull + public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() { + @Override + public ZenDeviceEffects createFromParcel(Parcel in) { + return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(), + in.readBoolean(), in.readBoolean(), in.readBoolean()); + } + + @Override + public ZenDeviceEffects[] newArray(int size) { + return new ZenDeviceEffects[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mGrayscale); + dest.writeBoolean(mSuppressAmbientDisplay); + dest.writeBoolean(mDimWallpaper); + dest.writeBoolean(mNightMode); + dest.writeBoolean(mDisableAutoBrightness); + dest.writeBoolean(mDisableTapToWake); + dest.writeBoolean(mDisableTiltToWake); + dest.writeBoolean(mDisableTouch); + dest.writeBoolean(mMinimizeRadioUsage); + dest.writeBoolean(mMaximizeDoze); + } + + /** Builder class for {@link ZenDeviceEffects} objects. */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final class Builder { + + private boolean mGrayscale; + private boolean mSuppressAmbientDisplay; + private boolean mDimWallpaper; + private boolean mNightMode; + private boolean mDisableAutoBrightness; + private boolean mDisableTapToWake; + private boolean mDisableTiltToWake; + private boolean mDisableTouch; + private boolean mMinimizeRadioUsage; + private boolean mMaximizeDoze; + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled). + */ + public Builder() { + } + + /** + * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding + * values in the supplied {@link ZenDeviceEffects}. + */ + public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) { + mGrayscale = zenDeviceEffects.shouldDisplayGrayscale(); + mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay(); + mDimWallpaper = zenDeviceEffects.shouldDimWallpaper(); + mNightMode = zenDeviceEffects.shouldUseNightMode(); + mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness(); + mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake(); + mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake(); + mDisableTouch = zenDeviceEffects.shouldDisableTouch(); + mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage(); + mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze(); + } + + /** + * Sets whether the level of color saturation of the display should be set to minimum, + * effectively switching it to grayscale, while the rule is active. + */ + @NonNull + public Builder setShouldDisplayGrayscale(boolean grayscale) { + mGrayscale = grayscale; + return this; + } + + /** + * Sets whether the ambient (always-on) display feature should be disabled while the rule + * is active. This will have no effect if the device doesn't support always-on display or if + * it's not generally enabled. + */ + @NonNull + public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) { + mSuppressAmbientDisplay = suppressAmbientDisplay; + return this; + } + + /** Sets whether the wallpaper should be dimmed while the rule is active. */ + @NonNull + public Builder setShouldDimWallpaper(boolean dimWallpaper) { + mDimWallpaper = dimWallpaper; + return this; + } + + /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */ + @NonNull + public Builder setShouldUseNightMode(boolean nightMode) { + mNightMode = nightMode; + return this; + } + + /** + * Sets whether the display's automatic brightness adjustment should be disabled while the + * rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) { + mDisableAutoBrightness = disableAutoBrightness; + return this; + } + + /** + * Sets whether "tap to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTapToWake(boolean disableTapToWake) { + mDisableTapToWake = disableTapToWake; + return this; + } + + /** + * Sets whether "tilt to wake" should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) { + mDisableTiltToWake = disableTiltToWake; + return this; + } + + /** + * Sets whether touch interactions should be disabled while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldDisableTouch(boolean disableTouch) { + mDisableTouch = disableTouch; + return this; + } + + /** + * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, + * should be minimized while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) { + mMinimizeRadioUsage = minimizeRadioUsage; + return this; + } + + /** + * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less + * frequent maintenance windows) while the rule is active. + * @hide + */ + @NonNull + public Builder setShouldMaximizeDoze(boolean maximizeDoze) { + mMaximizeDoze = maximizeDoze; + return this; + } + + /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */ + @NonNull + public ZenDeviceEffects build() { + return new ZenDeviceEffects(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, + mNightMode, mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, + mDisableTouch, mMinimizeRadioUsage, mMaximizeDoze); + } + } +} diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index ff6dffdaaf5d..1e08fd8061e0 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -85,6 +85,7 @@ public final class HotwordDetectedResult implements Parcelable { CONFIDENCE_LEVEL_HIGH, CONFIDENCE_LEVEL_VERY_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java index 7b3f47d04e48..26c1ca428c3b 100644 --- a/core/java/android/service/voice/HotwordRejectedResult.java +++ b/core/java/android/service/voice/HotwordRejectedResult.java @@ -22,6 +22,9 @@ import android.os.Parcelable; import com.android.internal.util.DataClass; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a result supporting the rejected hotword trigger. * @@ -57,6 +60,7 @@ public final class HotwordRejectedResult implements Parcelable { CONFIDENCE_LEVEL_MEDIUM, CONFIDENCE_LEVEL_HIGH }) + @Retention(RetentionPolicy.SOURCE) @interface HotwordConfidenceLevelValue { } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 04ae0aff10d0..bf09ec181c1f 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -273,8 +273,8 @@ public abstract class WallpaperService extends Service { int mCurHeight; float mZoom = 0f; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS - | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; + int mWindowPrivateFlags = + WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; Rect mPreviewSurfacePosition; diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 35834fd3886f..e6fcc0c391b2 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; +import android.util.CloseGuard; import android.util.Log; import android.util.Slog; @@ -46,6 +47,7 @@ import com.android.internal.R; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.Reference; import java.util.List; import java.util.Objects; import java.util.Queue; @@ -59,6 +61,9 @@ import java.util.concurrent.LinkedBlockingQueue; * {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be * invoked only from the main application thread. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p>The implementation of this API is likely to stream audio to remote servers to perform speech * recognition. As such this API is not intended to be used for continuous recognition, which would * consume a significant amount of battery and bandwidth. @@ -301,6 +306,8 @@ public class SpeechRecognizer { /** The actual RecognitionService endpoint */ private IRecognitionService mService; + private final CloseGuard mCloseGuard = new CloseGuard(); + /** Context with which the manager was created */ private final Context mContext; @@ -366,9 +373,7 @@ public class SpeechRecognizer { * {@link #createSpeechRecognizer} static factory method */ private SpeechRecognizer(final Context context, final ComponentName serviceComponent) { - mContext = context; - mServiceComponent = serviceComponent; - mOnDevice = false; + this(context, serviceComponent, false); } /** @@ -376,9 +381,17 @@ public class SpeechRecognizer { * {@link #createOnDeviceSpeechRecognizer} static factory method */ private SpeechRecognizer(final Context context, boolean onDevice) { + this(context, null, onDevice); + } + + private SpeechRecognizer( + final Context context, + final ComponentName serviceComponent, + final boolean onDevice) { mContext = context; - mServiceComponent = null; + mServiceComponent = serviceComponent; mOnDevice = onDevice; + mCloseGuard.open("SpeechRecognizer#destroy()"); } /** @@ -418,6 +431,9 @@ public class SpeechRecognizer { * command to the created {@code SpeechRecognizer}, otherwise no notifications will be * received. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p>For apps targeting Android 11 (API level 30) interaction with a speech recognition * service requires <queries> element to be added to the manifest file: * <pre>{@code @@ -445,6 +461,9 @@ public class SpeechRecognizer { * Use this version of the method to specify a specific service to direct this * {@link SpeechRecognizer} to. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * <p><strong>Important</strong>: before calling this method, please check via * {@link android.content.pm.PackageManager#queryIntentServices(Intent, int)} that {@code * serviceComponent} actually exists and provides @@ -486,6 +505,9 @@ public class SpeechRecognizer { * before dispatching any command to the created {@code SpeechRecognizer}, otherwise no * notifications will be received. * + * <p><strong>Important:</strong> the caller MUST invoke {@link #destroy()} on a + * SpeechRecognizer object when it is no longer needed. + * * @param context in which to create {@code SpeechRecognizer} * @return a new on-device {@code SpeechRecognizer}. * @throws UnsupportedOperationException iff {@link #isOnDeviceRecognitionAvailable(Context)} @@ -870,7 +892,7 @@ public class SpeechRecognizer { } private boolean checkOpenConnection() { - if (mService != null) { + if (mService != null && mService.asBinder().isBinderAlive()) { return true; } mListener.onError(ERROR_CLIENT); @@ -886,17 +908,32 @@ public class SpeechRecognizer { /** Destroys the {@code SpeechRecognizer} object. */ public void destroy() { - if (mService != null) { - try { - mService.cancel(mListener, /*isShutdown*/ true); - } catch (final Exception e) { - // Not important + try { + if (mService != null) { + try { + mService.cancel(mListener, /*isShutdown*/ true); + } catch (final Exception e) { + // Not important + } } + + mService = null; + mPendingTasks.clear(); + mListener.mInternalListener = null; + mCloseGuard.close(); + } finally { + Reference.reachabilityFence(this); } + } - mService = null; - mPendingTasks.clear(); - mListener.mInternalListener = null; + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + destroy(); + } finally { + super.finalize(); + } } /** Establishes a connection to system server proxy and initializes the session. */ diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 766e924c994e..b91a8789a181 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -163,12 +163,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS = "settings_remoteauth_enrollment"; - /** Flag to enable/disable entire page in Accessibility -> Hearing aids - * @hide - */ - public static final String SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE = - "settings_accessibility_hearing_aid_page"; - /** * Flag to enable/disable preferring the AccessibilityMenu service in the system. * @hide @@ -244,7 +238,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); - DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "true"); DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false"); DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false"); DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true"); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 6c3b8ab19792..17bbee6d020f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -114,8 +114,6 @@ interface IWindowManager IWindowSession openSession(in IWindowSessionCallback callback); - boolean useBLAST(); - @UnsupportedAppUsage void getInitialDisplaySize(int displayId, out Point size); @UnsupportedAppUsage diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 02e97dae70e2..d6535d477e5d 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -296,9 +296,11 @@ interface IWindowSession { /** * Request the server to call setInputWindowInfo on a given Surface, and return - * an input channel where the client can receive input. + * an input channel where the client can receive input. For windows, the clientToken should be + * the IWindow binder object. For other requests, the token can be any unique IBinder token to + * be used as unique identifier. */ - void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window, + void grantInputChannel(int displayId, in SurfaceControl surface, in IBinder clientToken, in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type, in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel); diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 45b3fdd7e5bc..7e388d4b79cb 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -26,6 +26,7 @@ import android.graphics.Region; import android.gui.TouchOcclusionMode; import android.os.IBinder; import android.os.InputConfig; +import android.util.Size; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -82,15 +83,11 @@ public final class InputWindowHandle { public IBinder token; /** - * The {@link IWindow} handle if InputWindowHandle is associated with a window, null otherwise. + * The {@link IBinder} handle if InputWindowHandle is associated with a client token, + * normally the IWindow token, null otherwise. */ @Nullable private IBinder windowToken; - /** - * Used to cache IWindow from the windowToken so we don't need to convert every time getWindow - * is called. - */ - private IWindow window; // The window name. public String name; @@ -106,6 +103,9 @@ public final class InputWindowHandle { // Window frame. public final Rect frame = new Rect(); + // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0 + public Size contentSize = new Size(0, 0); + public int surfaceInset; // Global scaling factor applied to touch events when they are dispatched @@ -177,7 +177,6 @@ public final class InputWindowHandle { inputApplicationHandle = new InputApplicationHandle(other.inputApplicationHandle); token = other.token; windowToken = other.windowToken; - window = other.window; name = other.name; layoutParamsFlags = other.layoutParamsFlags; layoutParamsType = other.layoutParamsType; @@ -199,6 +198,7 @@ public final class InputWindowHandle { transform.set(other.transform); } focusTransferTarget = other.focusTransferTarget; + contentSize = new Size(other.contentSize.getWidth(), other.contentSize.getHeight()); } @Override @@ -211,6 +211,7 @@ public final class InputWindowHandle { .append(", windowToken=").append(windowToken) .append(", displayId=").append(displayId) .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0) + .append(", contentSize=").append(contentSize) .toString(); } @@ -243,23 +244,14 @@ public final class InputWindowHandle { touchableRegionSurfaceControl = new WeakReference<>(bounds); } - public void setWindowToken(IWindow iwindow) { - windowToken = iwindow.asBinder(); - window = iwindow; + public void setWindowToken(IBinder iwindow) { + windowToken = iwindow; } public @Nullable IBinder getWindowToken() { return windowToken; } - public IWindow getWindow() { - if (window != null) { - return window; - } - window = IWindow.Stub.asInterface(windowToken); - return window; - } - /** * Set the provided inputConfig flag values. * @param inputConfig the flag values to change diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index b17d2d1800e5..c6601e8d3085 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2065,6 +2065,7 @@ public class KeyEvent extends InputEvent implements Parcelable { case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN: case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT: case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: + case KeyEvent.KEYCODE_STEM_PRIMARY: return true; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ac2c5509cac4..145af2e7ab67 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -442,9 +442,6 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mForceNextConfigUpdate; - private boolean mUseBLASTAdapter; - private boolean mForceDisableBLAST; - /** lazily-initialized in getAudioManager() */ private boolean mFastScrollSoundEffectsEnabled = false; @@ -1290,8 +1287,6 @@ public final class ViewRootImpl implements ViewParent, if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } - mWindowAttributes.privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; attrs = mWindowAttributes; setTag(); @@ -1502,9 +1497,6 @@ public final class ViewRootImpl implements ViewParent, Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: " + mAttachInfo.mDisplayState, new Throwable()); } - if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) { - mUseBLASTAdapter = true; - } if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = @@ -1901,8 +1893,7 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.insetsFlags.appearance = appearance; mWindowAttributes.insetsFlags.behavior = behavior; mWindowAttributes.privateFlags |= compatibleWindowFlag - | appearanceAndBehaviorPrivateFlags - | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; + | appearanceAndBehaviorPrivateFlags; if (mWindowAttributes.preservePreviousSurfaceInsets) { // Restore old surface insets. @@ -8692,11 +8683,7 @@ public final class ViewRootImpl implements ViewParent, } if (mSurfaceControl.isValid()) { - if (!useBLAST()) { - mSurface.copyFrom(mSurfaceControl); - } else { - updateBlastSurfaceIfNeeded(); - } + updateBlastSurfaceIfNeeded(); if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); } @@ -11533,7 +11520,7 @@ public final class ViewRootImpl implements ViewParent, SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); transaction.setBlurRegions(surfaceControl, regionCopy); - if (useBLAST() && mBlastBufferQueue != null) { + if (mBlastBufferQueue != null) { mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber); } } @@ -11550,18 +11537,6 @@ public final class ViewRootImpl implements ViewParent, mUnbufferedInputSource = mView.mUnbufferedInputSource; } - /** - * Force disabling use of the BLAST adapter regardless of the system - * flag. Needs to be called before addView. - */ - void forceDisableBLAST() { - mForceDisableBLAST = true; - } - - boolean useBLAST() { - return mUseBLASTAdapter && !mForceDisableBLAST; - } - int getSurfaceSequenceId() { return mSurfaceSequenceId; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c73514250e3b..92ce9f7e5d4c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -651,6 +651,7 @@ public interface WindowManager extends ViewManager { REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY, REMOVE_CONTENT_MODE_DESTROY, }) + @Retention(RetentionPolicy.SOURCE) @interface RemoveContentMode {} /** @@ -685,6 +686,7 @@ public interface WindowManager extends ViewManager { DISPLAY_IME_POLICY_FALLBACK_DISPLAY, DISPLAY_IME_POLICY_HIDE, }) + @Retention(RetentionPolicy.SOURCE) @interface DisplayImePolicy {} /** @@ -3249,13 +3251,6 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24; /** - * Flag to request creation of a BLAST (Buffer as LayerState) Layer. - * If not specified the client will receive a BufferQueue layer. - * @hide - */ - public static final int PRIVATE_FLAG_USE_BLAST = 1 << 25; - - /** * Flag to indicate that the window is controlling the appearance of system bars. So we * don't need to adjust it by reading its system UI flags for compatibility. * @hide @@ -3340,7 +3335,6 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, - PRIVATE_FLAG_USE_BLAST, PRIVATE_FLAG_APPEARANCE_CONTROLLED, PRIVATE_FLAG_BEHAVIOR_CONTROLLED, PRIVATE_FLAG_FIT_INSETS_CONTROLLED, @@ -3349,6 +3343,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP, PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, }) + @Retention(RetentionPolicy.SOURCE) public @interface PrivateFlags {} /** @@ -3438,10 +3433,6 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, name = "COLOR_SPACE_AGNOSTIC"), @ViewDebug.FlagToString( - mask = PRIVATE_FLAG_USE_BLAST, - equals = PRIVATE_FLAG_USE_BLAST, - name = "USE_BLAST"), - @ViewDebug.FlagToString( mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED, equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED, name = "APPEARANCE_CONTROLLED"), diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index d20d95d50d61..214f1ec3d1ec 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -60,8 +60,6 @@ import java.util.function.IntConsumer; public final class WindowManagerGlobal { private static final String TAG = "WindowManager"; - private static boolean sUseBLASTAdapter = false; - /** * This is the first time the window is being drawn, * so the client must call drawingFinished() when done @@ -99,7 +97,6 @@ public final class WindowManagerGlobal { public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1; public static final int ADD_FLAG_APP_VISIBLE = 0x2; - public static final int ADD_FLAG_USE_BLAST = 0x8; /** * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the @@ -176,7 +173,6 @@ public final class WindowManagerGlobal { if (sWindowManagerService != null) { ValueAnimator.setDurationScale( sWindowManagerService.getCurrentAnimatorScale()); - sUseBLASTAdapter = sWindowManagerService.useBLAST(); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -218,13 +214,6 @@ public final class WindowManagerGlobal { } } - /** - * Whether or not to use BLAST for ViewRootImpl - */ - public static boolean useBLAST() { - return sUseBLASTAdapter; - } - @UnsupportedAppUsage public String[] getViewRootNames() { synchronized (mLock) { diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 652fe2445ddc..da310780a813 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -228,14 +228,14 @@ public class WindowlessWindowManager implements IWindowSession { if (mRealWm instanceof IWindowSession.Stub) { mRealWm.grantInputChannel(displayId, new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"), - window, mHostInputToken, attrs.flags, attrs.privateFlags, + window.asBinder(), mHostInputToken, attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token, state.mInputTransferToken, attrs.getTitle().toString(), outInputChannel); } else { - mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, - attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token, - state.mInputTransferToken, attrs.getTitle().toString(), + mRealWm.grantInputChannel(displayId, sc, window.asBinder(), mHostInputToken, + attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type, + attrs.token, state.mInputTransferToken, attrs.getTitle().toString(), outInputChannel); } state.mInputChannelToken = @@ -245,8 +245,7 @@ public class WindowlessWindowManager implements IWindowSession { } } - final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE | - WindowManagerGlobal.ADD_FLAG_USE_BLAST; + final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE; sendLayoutParamsToParent(); // Include whether the window is in touch mode. @@ -593,7 +592,7 @@ public class WindowlessWindowManager implements IWindowSession { List<Rect> unrestrictedRects) {} @Override - public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, + public void grantInputChannel(int displayId, SurfaceControl surface, IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken, IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) { diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index ab9566e1ece0..5296b9992542 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -17,6 +17,13 @@ flag { } flag { + name: "deduplicate_accessibility_warning_dialog" + namespace: "accessibility" + description: "Removes duplicate definition of the accessibility warning dialog." + bug: "303511250" +} + +flag { namespace: "accessibility" name: "force_invert_color" description: "Enable force force-dark for smart inversion and dark theme everywhere" diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java index c4d43bcfdb24..eb2a101e8ee6 100644 --- a/core/java/android/view/inputmethod/HandwritingGesture.java +++ b/core/java/android/view/inputmethod/HandwritingGesture.java @@ -86,6 +86,7 @@ public abstract class HandwritingGesture { * Granular level on which text should be operated. */ @IntDef({GRANULARITY_CHARACTER, GRANULARITY_WORD}) + @Retention(RetentionPolicy.SOURCE) @interface Granularity {} /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index eeab005771f5..589b7a3eeda4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1176,24 +1176,33 @@ public final class InputMethodManager { mActive = interactive; mFullscreenMode = fullscreen; if (interactive) { + // Find the next view focus to start the input connection when the + // device was interactive. final View rootView = mCurRootView != null ? mCurRootView.getView() : null; if (rootView == null) { + // No window focused or view was removed, ignore request. return; } - // Find the next view focus to start the input connection when the - // device was interactive. final ViewRootImpl currentViewRootImpl = mCurRootView; + // Post this on UI thread as required for view focus code. rootView.post(() -> { synchronized (mH) { if (mCurRootView != currentViewRootImpl) { + // Focused window changed since posting, ignore request. return; } } - final View focusedView = currentViewRootImpl.getView().findFocus(); + final View curRootView = currentViewRootImpl.getView(); + if (curRootView == null) { + // View was removed, ignore request. + return; + } + final View focusedView = curRootView.findFocus(); onViewFocusChangedInternal(focusedView, focusedView != null); }); } else { + // Finish input connection when device becomes non-interactive. finishInputLocked(); if (isImeSessionAvailableLocked()) { mCurBindState.mImeSession.finishInput(); diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java index e44f43609256..4816f35e6a07 100644 --- a/core/java/android/window/BackNavigationInfo.java +++ b/core/java/android/window/BackNavigationInfo.java @@ -27,6 +27,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteCallback; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Information to be sent to SysUI about a back event. * @@ -85,6 +88,7 @@ public final class BackNavigationInfo implements Parcelable { TYPE_CROSS_TASK, TYPE_CALLBACK }) + @Retention(RetentionPolicy.SOURCE) public @interface BackTargetType { } diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 0ce076b6eb96..1bd921b339f6 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -22,6 +22,8 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * The window frame container class used by client side for layout. * @hide @@ -101,6 +103,29 @@ public class ClientWindowFrames implements Parcelable { } @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ClientWindowFrames other = (ClientWindowFrames) o; + return frame.equals(other.frame) + && displayFrame.equals(other.displayFrame) + && parentFrame.equals(other.parentFrame) + && Objects.equals(attachedFrame, other.attachedFrame) + && isParentFrameClippedByDisplayCutout == other.isParentFrameClippedByDisplayCutout + && compatScale == other.compatScale; + } + + @Override + public int hashCode() { + return Objects.hash(frame, displayFrame, parentFrame, attachedFrame, + isParentFrameClippedByDisplayCutout, compatScale); + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 758582615a46..cc875ad7ad1b 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -37,7 +37,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; @@ -426,7 +425,7 @@ public class SnapshotDrawerUtils { // Setting as trusted overlay to let touches pass through. This is safe because this // window is controlled by the system. layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) - | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST; + | PRIVATE_FLAG_TRUSTED_OVERLAY; layoutParams.token = token; layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; diff --git a/core/java/android/window/SplashScreen.java b/core/java/android/window/SplashScreen.java index f1c0d8dee525..b6c04d94b476 100644 --- a/core/java/android/window/SplashScreen.java +++ b/core/java/android/window/SplashScreen.java @@ -33,6 +33,8 @@ import android.util.Log; import android.util.Singleton; import android.util.Slog; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; /** @@ -65,6 +67,7 @@ public interface SplashScreen { SPLASH_SCREEN_STYLE_SOLID_COLOR, SPLASH_SCREEN_STYLE_ICON }) + @Retention(RetentionPolicy.SOURCE) @interface SplashScreenStyle {} /** diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index b63a969337e0..9fe30df13036 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -6,4 +6,12 @@ flag { description: "When necessary, configuration decoupled from status bar and display cutout" bug: "291870756" is_fixed_read_only: true +} + +flag { + name: "movable_cutout_configuration" + namespace: "large_screen_experiences_app_compat" + description: "Make it possible to move cutout across edges through device config" + bug: "302387383" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 0ad6c99422b8..b600b22751ff 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -35,4 +35,11 @@ flag { name: "fullscreen_dim_flag" description: "Whether to allow showing fullscreen dim on ActivityEmbedding split" bug: "253533308" +} + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_interactive_divider_flag" + description: "Whether the interactive divider feature is enabled" + bug: "293654166" }
\ No newline at end of file diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java index 64974090938f..2b6913ca5e5a 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java @@ -34,6 +34,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuM */ class AccessibilityServiceTarget extends AccessibilityTarget { + private final AccessibilityServiceInfo mAccessibilityServiceInfo; + AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType, @AccessibilityFragmentType int fragmentType, @NonNull AccessibilityServiceInfo serviceInfo) { @@ -47,6 +49,7 @@ class AccessibilityServiceTarget extends AccessibilityTarget { serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()), serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()), convertToKey(convertToUserType(shortcutType))); + mAccessibilityServiceInfo = serviceInfo; } @Override @@ -64,4 +67,8 @@ class AccessibilityServiceTarget extends AccessibilityTarget { holder.mLabelView.setEnabled(enabled); holder.mStatusView.setEnabled(enabled); } + + public AccessibilityServiceInfo getAccessibilityServiceInfo() { + return mAccessibilityServiceInfo; + } } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java new file mode 100644 index 000000000000..0f8ced27e8c1 --- /dev/null +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceWarning.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.accessibility.dialog; + +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.BidiFormatter; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; + +/** + * Utility class for creating the dialog that asks the user for explicit permission + * before an accessibility service is enabled. + */ +public class AccessibilityServiceWarning { + + /** + * Returns an {@link AlertDialog} to be shown to confirm that the user + * wants to enable an {@link android.accessibilityservice.AccessibilityService}. + */ + public static AlertDialog createAccessibilityServiceWarningDialog(@NonNull Context context, + @NonNull AccessibilityServiceInfo info, + @NonNull View.OnClickListener allowListener, + @NonNull View.OnClickListener denyListener, + @NonNull View.OnClickListener uninstallListener) { + final AlertDialog ad = new AlertDialog.Builder(context) + .setView(createAccessibilityServiceWarningDialogContentView( + context, info, allowListener, denyListener, uninstallListener)) + .setCancelable(true) + .create(); + Window window = ad.getWindow(); + WindowManager.LayoutParams params = window.getAttributes(); + params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + params.type = WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; + window.setAttributes(params); + return ad; + } + + @VisibleForTesting + public static View createAccessibilityServiceWarningDialogContentView(Context context, + AccessibilityServiceInfo info, + View.OnClickListener allowListener, + View.OnClickListener denyListener, + View.OnClickListener uninstallListener) { + final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); + final View content = inflater.inflate(R.layout.accessibility_service_warning, null); + + final Drawable icon; + if (info.getResolveInfo().getIconResource() == 0) { + icon = context.getDrawable(R.drawable.ic_accessibility_generic); + } else { + icon = info.getResolveInfo().loadIcon(context.getPackageManager()); + } + final ImageView permissionDialogIcon = content.findViewById( + R.id.accessibility_permissionDialog_icon); + permissionDialogIcon.setImageDrawable(icon); + + final TextView permissionDialogTitle = content.findViewById( + R.id.accessibility_permissionDialog_title); + permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title, + getServiceName(context, info))); + + final Button permissionAllowButton = content.findViewById( + R.id.accessibility_permission_enable_allow_button); + final Button permissionDenyButton = content.findViewById( + R.id.accessibility_permission_enable_deny_button); + permissionAllowButton.setOnClickListener(allowListener); + permissionAllowButton.setOnTouchListener(getTouchConsumingListener()); + permissionDenyButton.setOnClickListener(denyListener); + + final Button uninstallButton = content.findViewById( + R.id.accessibility_permission_enable_uninstall_button); + // Show an uninstall button to help users quickly remove non-preinstalled apps. + if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) { + uninstallButton.setVisibility(View.VISIBLE); + uninstallButton.setOnClickListener(uninstallListener); + } + return content; + } + + @VisibleForTesting + @SuppressLint("ClickableViewAccessibility") // Touches are intentionally consumed + public static View.OnTouchListener getTouchConsumingListener() { + return (view, event) -> { + // Filter obscured touches by consuming them. + if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) + || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) { + if (event.getAction() == MotionEvent.ACTION_UP) { + Toast.makeText(view.getContext(), + R.string.accessibility_dialog_touch_filtered_warning, + Toast.LENGTH_SHORT).show(); + } + return true; + } + return false; + }; + } + + // Get the service name and bidi wrap it to protect from bidi side effects. + private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) { + final Locale locale = context.getResources().getConfiguration().getLocales().get(0); + final CharSequence label = + info.getResolveInfo().loadLabel(context.getPackageManager()); + return BidiFormatter.getInstance(locale).unicodeWrap(label); + } +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 987c14c6ab51..d4eccd458e35 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -28,6 +28,7 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.app.Dialog; import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; @@ -56,7 +57,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { "accessibility_shortcut_menu_mode"; private final List<AccessibilityTarget> mTargets = new ArrayList<>(); private AlertDialog mMenuDialog; - private AlertDialog mPermissionDialog; + private Dialog mPermissionDialog; private ShortcutTargetAdapter mTargetAdapter; @Override @@ -123,7 +124,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { if (target instanceof AccessibilityServiceTarget) { showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, - mTargetAdapter); + position, mTargetAdapter); return; } } @@ -149,20 +150,43 @@ public class AccessibilityShortcutChooserActivity extends Activity { } private void showPermissionDialogIfNeeded(Context context, - AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) { + AccessibilityServiceTarget serviceTarget, int position, + ShortcutTargetAdapter targetAdapter) { if (mPermissionDialog != null) { return; } - mPermissionDialog = new AlertDialog.Builder(context) - .setView(createEnableDialogContentView(context, serviceTarget, - v -> { - mPermissionDialog.dismiss(); - targetAdapter.notifyDataSetChanged(); - }, - v -> mPermissionDialog.dismiss())) - .setOnDismissListener(dialog -> mPermissionDialog = null) - .create(); + if (Flags.deduplicateAccessibilityWarningDialog()) { + mPermissionDialog = AccessibilityServiceWarning + .createAccessibilityServiceWarningDialog(context, + serviceTarget.getAccessibilityServiceInfo(), + v -> { + serviceTarget.onCheckedChanged(true); + targetAdapter.notifyDataSetChanged(); + mPermissionDialog.dismiss(); + }, v -> { + serviceTarget.onCheckedChanged(false); + mPermissionDialog.dismiss(); + }, + v -> { + mTargets.remove(position); + context.getPackageManager().getPackageInstaller().uninstall( + serviceTarget.getComponentName().getPackageName(), null); + targetAdapter.notifyDataSetChanged(); + mPermissionDialog.dismiss(); + }); + mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null); + } else { + mPermissionDialog = new AlertDialog.Builder(context) + .setView(createEnableDialogContentView(context, serviceTarget, + v -> { + mPermissionDialog.dismiss(); + targetAdapter.notifyDataSetChanged(); + }, + v -> mPermissionDialog.dismiss())) + .setOnDismissListener(dialog -> mPermissionDialog = null) + .create(); + } mPermissionDialog.show(); } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java index 0f85075a0d6c..51a5ddfa8dd6 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java @@ -296,6 +296,10 @@ public final class AccessibilityTargetHelper { } } + /** + * @deprecated Use {@link AccessibilityServiceWarning}. + */ + @Deprecated static View createEnableDialogContentView(Context context, AccessibilityServiceTarget target, View.OnClickListener allowListener, View.OnClickListener denyListener) { diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java index 1826f7a38e26..b0f35784fbbd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivity.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,19 +26,6 @@ import java.util.Set; //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public interface ParsedActivity extends ParsedMainComponent { - /** - * Generate activity object that forwards user to App Details page automatically. - * This activity should be invisible to user and user should not know or see it. - * @hide - */ - @NonNull - static ParsedActivity makeAppDetailsActivity(String packageName, String processName, - int uiOptions, String taskAffinity, boolean hardwareAccelerated) { - // Proxy method since ParsedActivityImpl is supposed to be package visibility - return ParsedActivityImpl.makeAppDetailsActivity(packageName, processName, uiOptions, - taskAffinity, hardwareAccelerated); - } - int getColorMode(); int getConfigChanges(); diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java index cf478b1da2e4..adb5f4f6d2a2 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java index 1a5d110ca8ff..5b623cdbaebb 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttribution.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttribution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.StringRes; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java index 5b6ecbac1359..319ed7f23d47 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java index c325d8dab296..76c500827a32 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentation.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentation.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java index a7f7b0086c00..fee0017c4a31 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfo.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java index b926d5345149..291ed0c44ddd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponent.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java index dc5347a41fcf..813d14dc61b3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java index 64f4fbd440a2..a5ba51307f01 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroup.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; /** @hide */ //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java index 608d08eeb984..e5247f99f398 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcess.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcess.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.SuppressLint; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java index c66a5c1b6c92..ba5470f12017 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProvider.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java b/core/java/com/android/internal/pm/pkg/component/ParsedService.java index 5fc251ccab47..e3611021442e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedService.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java index e17d1c4f5184..984d50ddfc70 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermission.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermission.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.pm.pkg.component; +package com.android.internal.pm.pkg.component; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 686e1fc2c34e..9d0be4bf8ee6 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -40,6 +40,7 @@ import java.util.function.IntFunction; /** * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrayUtils { private static final int CACHE_SIZE = 73; private static Object[] sCache = new Object[CACHE_SIZE]; @@ -48,35 +49,43 @@ public class ArrayUtils { private ArrayUtils() { /* cannot be instantiated */ } + @android.ravenwood.annotation.RavenwoodReplace public static byte[] newUnpaddedByteArray(int minLen) { return (byte[])VMRuntime.getRuntime().newUnpaddedArray(byte.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static char[] newUnpaddedCharArray(int minLen) { return (char[])VMRuntime.getRuntime().newUnpaddedArray(char.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static int[] newUnpaddedIntArray(int minLen) { return (int[])VMRuntime.getRuntime().newUnpaddedArray(int.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static boolean[] newUnpaddedBooleanArray(int minLen) { return (boolean[])VMRuntime.getRuntime().newUnpaddedArray(boolean.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static long[] newUnpaddedLongArray(int minLen) { return (long[])VMRuntime.getRuntime().newUnpaddedArray(long.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static float[] newUnpaddedFloatArray(int minLen) { return (float[])VMRuntime.getRuntime().newUnpaddedArray(float.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace public static Object[] newUnpaddedObjectArray(int minLen) { return (Object[])VMRuntime.getRuntime().newUnpaddedArray(Object.class, minLen); } + @android.ravenwood.annotation.RavenwoodReplace @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @SuppressWarnings("unchecked") public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) { diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index ea3c70f5e60b..c3d21a4024f8 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -59,6 +59,7 @@ static struct { jfieldID layoutParamsType; jfieldID dispatchingTimeoutMillis; jfieldID frame; + jfieldID contentSize; jfieldID surfaceInset; jfieldID scaleFactor; jfieldID touchableRegion; @@ -281,6 +282,9 @@ jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowIn ScopedLocalRef<jobject> rectObj(env, JNICommon::objFromRect(env, windowInfo.frame)); env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.frame, rectObj.get()); + ScopedLocalRef<jobject> sizeObj(env, JNICommon::objFromSize(env, windowInfo.contentSize)); + env->SetObjectField(inputWindowHandle, gInputWindowHandleClassInfo.contentSize, sizeObj.get()); + env->SetIntField(inputWindowHandle, gInputWindowHandleClassInfo.surfaceInset, windowInfo.surfaceInset); env->SetFloatField(inputWindowHandle, gInputWindowHandleClassInfo.scaleFactor, @@ -393,6 +397,9 @@ int register_android_view_InputWindowHandle(JNIEnv* env) { GET_FIELD_ID(gInputWindowHandleClassInfo.frame, clazz, "frame", "Landroid/graphics/Rect;"); + GET_FIELD_ID(gInputWindowHandleClassInfo.contentSize, clazz, "contentSize", + "Landroid/util/Size;"); + GET_FIELD_ID(gInputWindowHandleClassInfo.surfaceInset, clazz, "surfaceInset", "I"); diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index b1dab85d2e27..8fa179b496b6 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -574,7 +574,7 @@ static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env, jobject th if (result != NO_ERROR) { return -1; } - return frameCount * channelCount * audio_bytes_per_sample(format); + return frameCount * audio_bytes_per_frame(channelCount, format); } static jboolean android_media_AudioRecord_setInputDevice( diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp index 95bf49fe501e..aebe7ea7ee61 100644 --- a/core/jni/android_os_PerformanceHintManager.cpp +++ b/core/jni/android_os_PerformanceHintManager.cpp @@ -16,15 +16,16 @@ #define LOG_TAG "PerfHint-jni" -#include "jni.h" - +#include <android/performance_hint.h> #include <dlfcn.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <utils/Log.h> + #include <vector> #include "core_jni_helpers.h" +#include "jni.h" namespace android { @@ -44,6 +45,11 @@ typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t); typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t); typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const); typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool); +typedef void (*APH_reportActualWorkDuration2)(APerformanceHintSession*, AWorkDuration*); + +typedef AWorkDuration* (*AWD_create)(); +typedef void (*AWD_setTimeNanos)(AWorkDuration*, int64_t); +typedef void (*AWD_release)(AWorkDuration*); bool gAPerformanceHintBindingInitialized = false; APH_getManager gAPH_getManagerFn = nullptr; @@ -56,6 +62,14 @@ APH_sendHint gAPH_sendHintFn = nullptr; APH_setThreads gAPH_setThreadsFn = nullptr; APH_getThreadIds gAPH_getThreadIdsFn = nullptr; APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr; +APH_reportActualWorkDuration2 gAPH_reportActualWorkDuration2Fn = nullptr; + +AWD_create gAWD_createFn = nullptr; +AWD_setTimeNanos gAWD_setWorkPeriodStartTimestampNanosFn = nullptr; +AWD_setTimeNanos gAWD_setActualTotalDurationNanosFn = nullptr; +AWD_setTimeNanos gAWD_setActualCpuDurationNanosFn = nullptr; +AWD_setTimeNanos gAWD_setActualGpuDurationNanosFn = nullptr; +AWD_release gAWD_releaseFn = nullptr; void ensureAPerformanceHintBindingInitialized() { if (gAPerformanceHintBindingInitialized) return; @@ -112,9 +126,46 @@ void ensureAPerformanceHintBindingInitialized() { (APH_setPreferPowerEfficiency)dlsym(handle_, "APerformanceHint_setPreferPowerEfficiency"); LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr, - "Failed to find required symbol" + "Failed to find required symbol " "APerformanceHint_setPreferPowerEfficiency!"); + gAPH_reportActualWorkDuration2Fn = + (APH_reportActualWorkDuration2)dlsym(handle_, + "APerformanceHint_reportActualWorkDuration2"); + LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDuration2Fn == nullptr, + "Failed to find required symbol " + "APerformanceHint_reportActualWorkDuration2!"); + + gAWD_createFn = (AWD_create)dlsym(handle_, "AWorkDuration_create"); + LOG_ALWAYS_FATAL_IF(gAWD_createFn == nullptr, + "Failed to find required symbol AWorkDuration_create!"); + + gAWD_setWorkPeriodStartTimestampNanosFn = + (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setWorkPeriodStartTimestampNanos"); + LOG_ALWAYS_FATAL_IF(gAWD_setWorkPeriodStartTimestampNanosFn == nullptr, + "Failed to find required symbol " + "AWorkDuration_setWorkPeriodStartTimestampNanos!"); + + gAWD_setActualTotalDurationNanosFn = + (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualTotalDurationNanos"); + LOG_ALWAYS_FATAL_IF(gAWD_setActualTotalDurationNanosFn == nullptr, + "Failed to find required symbol " + "AWorkDuration_setActualTotalDurationNanos!"); + + gAWD_setActualCpuDurationNanosFn = + (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualCpuDurationNanos"); + LOG_ALWAYS_FATAL_IF(gAWD_setActualCpuDurationNanosFn == nullptr, + "Failed to find required symbol AWorkDuration_setActualCpuDurationNanos!"); + + gAWD_setActualGpuDurationNanosFn = + (AWD_setTimeNanos)dlsym(handle_, "AWorkDuration_setActualGpuDurationNanos"); + LOG_ALWAYS_FATAL_IF(gAWD_setActualGpuDurationNanosFn == nullptr, + "Failed to find required symbol AWorkDuration_setActualGpuDurationNanos!"); + + gAWD_releaseFn = (AWD_release)dlsym(handle_, "AWorkDuration_release"); + LOG_ALWAYS_FATAL_IF(gAWD_releaseFn == nullptr, + "Failed to find required symbol AWorkDuration_release!"); + gAPerformanceHintBindingInitialized = true; } @@ -238,6 +289,25 @@ static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nati enabled); } +static void nativeReportActualWorkDuration2(JNIEnv* env, jclass clazz, jlong nativeSessionPtr, + jlong workPeriodStartTimestampNanos, + jlong actualTotalDurationNanos, + jlong actualCpuDurationNanos, + jlong actualGpuDurationNanos) { + ensureAPerformanceHintBindingInitialized(); + + AWorkDuration* workDuration = gAWD_createFn(); + gAWD_setWorkPeriodStartTimestampNanosFn(workDuration, workPeriodStartTimestampNanos); + gAWD_setActualTotalDurationNanosFn(workDuration, actualTotalDurationNanos); + gAWD_setActualCpuDurationNanosFn(workDuration, actualCpuDurationNanos); + gAWD_setActualGpuDurationNanosFn(workDuration, actualGpuDurationNanos); + + gAPH_reportActualWorkDuration2Fn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr), + workDuration); + + gAWD_releaseFn(workDuration); +} + static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeAcquireManager", "()J", (void*)nativeAcquireManager}, {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos}, @@ -249,6 +319,7 @@ static const JNINativeMethod gPerformanceHintMethods[] = { {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds}, {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency}, + {"nativeReportActualWorkDuration", "(JJJJJ)V", (void*)nativeReportActualWorkDuration2}, }; int register_android_os_PerformanceHintManager(JNIEnv* env) { diff --git a/core/jni/jni_common.cpp b/core/jni/jni_common.cpp index b81c9b6eed95..dd69b16f5a96 100644 --- a/core/jni/jni_common.cpp +++ b/core/jni/jni_common.cpp @@ -34,6 +34,11 @@ static struct { jfieldID top; } gRectClassInfo; +static struct { + jclass clazz; + jmethodID ctor; +} gSizeClassInfo; + Rect JNICommon::rectFromObj(JNIEnv* env, jobject rectObj) { int left = env->GetIntField(rectObj, gRectClassInfo.left); int top = env->GetIntField(rectObj, gRectClassInfo.top); @@ -47,6 +52,10 @@ jobject JNICommon::objFromRect(JNIEnv* env, Rect rect) { rect.right, rect.bottom); } +jobject JNICommon::objFromSize(JNIEnv* env, Size size) { + return env->NewObject(gSizeClassInfo.clazz, gSizeClassInfo.ctor, size.width, size.height); +} + int register_jni_common(JNIEnv* env) { jclass rectClazz = FindClassOrDie(env, "android/graphics/Rect"); gRectClassInfo.clazz = MakeGlobalRefOrDie(env, rectClazz); @@ -55,6 +64,11 @@ int register_jni_common(JNIEnv* env) { gRectClassInfo.left = GetFieldIDOrDie(env, rectClazz, "left", "I"); gRectClassInfo.right = GetFieldIDOrDie(env, rectClazz, "right", "I"); gRectClassInfo.top = GetFieldIDOrDie(env, rectClazz, "top", "I"); + + jclass sizeClazz = FindClassOrDie(env, "android/util/Size"); + gSizeClassInfo.clazz = MakeGlobalRefOrDie(env, sizeClazz); + gSizeClassInfo.ctor = GetMethodIDOrDie(env, sizeClazz, "<init>", "(II)V"); + return 0; } diff --git a/core/jni/jni_common.h b/core/jni/jni_common.h index d670a7d6bd59..f1c60148d6b6 100644 --- a/core/jni/jni_common.h +++ b/core/jni/jni_common.h @@ -14,14 +14,17 @@ * limitations under the License. */ #include <jni.h> +#include <ui/Size.h> namespace android { class Rect; +using ui::Size; class JNICommon { public: static Rect rectFromObj(JNIEnv* env, jobject rectObj); static jobject objFromRect(JNIEnv* env, Rect rect); + static jobject objFromSize(JNIEnv* env, Size size); }; } // namespace android
\ No newline at end of file diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6859f1fd0886..76ae3e0b516b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8391,6 +8391,10 @@ android:exported="true"> </provider> + <meta-data + android:name="com.android.server.patch.25239169" + android:value="true" /> + </application> </manifest> diff --git a/core/res/res/drawable/ic_accessibility_generic.xml b/core/res/res/drawable/ic_accessibility_generic.xml new file mode 100644 index 000000000000..68a89e688762 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_generic.xml @@ -0,0 +1,30 @@ +<!-- + Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M5.6875,22.8235C4.9092,22.4776 4.8184,22.2615 2.8752,16.1257 1.8439,12.8691 1.0015,10.0882 1.0033,9.946 1.0137,9.1246 1.3166,8.8389 6.25,4.9976 9.2052,2.6966 11.2442,1.1943 11.5332,1.1049 11.8724,0.9999 12.1235,0.996 12.432,1.0907 12.9214,1.2408 22.3634,8.7104 22.6857,9.2024 23.1266,9.8752 23.0768,10.1907 22.0053,13.5155 19.0153,22.7935 19.1481,22.461 18.2853,22.8286 17.7053,23.0757 6.2446,23.0711 5.6875,22.8235Z" + android:strokeWidth="0.31999999" + android:fillColor="#ced6da"/> + <path + android:pathData="M10.0615,19.3507C10.028,19.2609 9.9864,17.362 9.9691,15.1308L9.9375,11.0741 8.5,10.853c-2.1981,-0.3381 -2.1924,-0.3355 -2.1619,-0.978 0.0141,-0.2963 0.074,-0.587 0.1331,-0.6462 0.06,-0.06 0.7667,0.0113 1.5994,0.1614 2.1217,0.3824 5.7371,0.3824 7.8588,0 0.8206,-0.1479 1.5349,-0.2259 1.5874,-0.1733 0.0525,0.0526 0.1334,0.3334 0.1799,0.624 0.078,0.4881 0.0598,0.5378 -0.2384,0.6512 -0.1776,0.0675 -1.0143,0.2259 -1.8593,0.352l-1.5364,0.2293 -0.0625,4.182 -0.0625,4.182l-0.625,0 -0.625,0l-0.0625,-1.875 -0.0625,-1.875l-0.5625,0L11.4375,15.6875l-0.0625,1.875 -0.0625,1.875 -0.595,0.0382c-0.4038,0.0259 -0.6146,-0.0143 -0.6559,-0.125zM11.3716,8.912c-0.4861,-0.3351 -0.6133,-0.5622 -0.6176,-1.1029 -0.0047,-0.6005 0.2255,-0.9684 0.739,-1.1811 0.8994,-0.3726 1.7571,0.2075 1.7571,1.1885 0,0.4533 -0.0659,0.5905 -0.4418,0.9206 -0.5007,0.4396 -0.9697,0.4967 -1.4366,0.1749z" + android:strokeWidth="0.31999999" + android:fillColor="#ffffff"/> +</vector> diff --git a/core/res/res/layout/accessibility_service_warning.xml b/core/res/res/layout/accessibility_service_warning.xml new file mode 100644 index 000000000000..0381facd3628 --- /dev/null +++ b/core/res/res/layout/accessibility_service_warning.xml @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:textDirection="locale" + android:scrollbarStyle="outsideOverlay" + android:gravity="top"> + + <LinearLayout + android:accessibilityDataSensitive="yes" + style="@style/AccessibilityDialog"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal" + android:paddingLeft="24dp" + android:paddingRight="24dp"> + + <ImageView + android:id="@+id/accessibility_permissionDialog_icon" + style="@style/AccessibilityDialogServiceIcon" /> + + <TextView + android:id="@+id/accessibility_permissionDialog_title" + style="@style/AccessibilityDialogTitle" /> + + <TextView + android:id="@+id/permissionDialog_description" + android:text="@string/accessibility_service_warning_description" + style="@style/AccessibilityDialogDescription" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginBottom="24dp" > + + <ImageView + android:id="@+id/controlScreen_icon" + android:src="@drawable/ic_visibility" + style="@style/AccessibilityDialogIcon" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@+id/controlScreen_title" + android:text="@string/accessibility_service_screen_control_title" + style="@style/AccessibilityDialogPermissionTitle" /> + + <TextView + android:id="@+id/controlScreen_description" + android:text="@string/accessibility_service_screen_control_description" + style="@style/AccessibilityDialogPermissionDescription" /> + + </LinearLayout> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginBottom="24dp" > + + <ImageView + android:id="@+id/performAction_icon" + android:src="@drawable/ic_pan_tool" + style="@style/AccessibilityDialogIcon" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + android:id="@+id/performAction_title" + android:text="@string/accessibility_service_action_perform_title" + style="@style/AccessibilityDialogPermissionTitle" /> + + <TextView + android:id="@+id/performAction_description" + android:text="@string/accessibility_service_action_perform_description" + style="@style/AccessibilityDialogPermissionDescription" /> + + </LinearLayout> + + </LinearLayout> + + </LinearLayout> + + <!-- Buttons on bottom of dialog --> + <LinearLayout + style="@style/AccessibilityDialogButtonList"> + + <Space + style="@style/AccessibilityDialogButtonBarSpace"/> + + <Button + android:id="@+id/accessibility_permission_enable_allow_button" + android:text="@string/accessibility_dialog_button_allow" + style="@style/AccessibilityDialogButton" /> + + <Button + android:id="@+id/accessibility_permission_enable_deny_button" + android:text="@string/accessibility_dialog_button_deny" + style="@style/AccessibilityDialogButton" /> + + <Button + android:id="@+id/accessibility_permission_enable_uninstall_button" + android:text="@string/accessibility_dialog_button_uninstall" + android:visibility="gone" + style="@style/AccessibilityDialogButton" /> + </LinearLayout> + </LinearLayout> + +</ScrollView> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3496994fe173..698c5bad3801 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4303,6 +4303,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is true.--> <attr name="requireDeviceScreenOn" format="boolean"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode" format="boolean"/> </declare-styleable> <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that @@ -4327,6 +4330,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is false.--> <attr name="requireDeviceScreenOn"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode"/> </declare-styleable> <!-- Specify one or more <code>aid-group</code> elements inside a diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 30beee0d02a1..eddd81e78692 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -521,7 +521,7 @@ <color name="system_surface_dim_dark">#121316</color> <color name="system_surface_variant_dark">#44474F</color> <color name="system_on_surface_variant_dark">#C4C6D0</color> - <color name="system_outline_dark">#72747D</color> + <color name="system_outline_dark">#8E9099</color> <color name="system_outline_variant_dark">#444746</color> <color name="system_error_dark">#FFB4A8</color> <color name="system_on_error_dark">#690001</color> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 764279490643..39d958cdf5ee 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -205,6 +205,13 @@ <string name="config_satellite_emergency_handover_intent_action" translatable="false"></string> <java-symbol type="string" name="config_satellite_emergency_handover_intent_action" /> + <!-- Whether outgoing satellite datagrams should be sent to modem in demo mode. When satellite + is enabled for demo mode, if this config is enabled, outgoing datagrams will be sent to + modem; otherwise, success results will be returned. If demo mode is disabled, outgoing + datagrams are always sent to modem. --> + <bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool> + <java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" /> + <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks will not perform handover if the target transport is out of service, or VoPS not supported. The network will be torn down on the source transport, and will be diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index cec83dee9c3a..eed186ad3702 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4701,6 +4701,13 @@ <string name="accessibility_dialog_button_allow">Allow</string> <!-- String for the deny button in accessibility permission dialog. [CHAR LIMIT=10] --> <string name="accessibility_dialog_button_deny">Deny</string> + <!-- String for the uninstall button in accessibility permission dialog. --> + <string name="accessibility_dialog_button_uninstall">Uninstall</string> + <!-- Warning shown when user input has been blocked due to another app overlaying screen + content. Since we don't know what the app is showing on top of the input target, we + can't verify user consent. [CHAR LIMIT=NONE] --> + <string name="accessibility_dialog_touch_filtered_warning">An app is obscuring the permission + request so your response cannot be verified.</string> <!-- Title for accessibility select shortcut menu dialog. [CHAR LIMIT=100] --> <string name="accessibility_select_shortcut_menu_title">Tap a feature to start using it:</string> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 13d04e53b508..619ec31e37bc 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1560,4 +1560,87 @@ please see styles_device_defaults.xml. <!-- The default style for input method switch dialog --> <style name="InputMethodSwitchDialogStyle" parent="AlertDialog.DeviceDefault"> </style> + + <style name="AccessibilityDialog"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:orientation">vertical</item> + <item name="android:divider">@*android:drawable/list_divider_material</item> + <item name="android:showDividers">middle</item> + </style> + + <style name="AccessibilityDialogServiceIcon"> + <item name="android:layout_width">36dp</item> + <item name="android:layout_height">36dp</item> + <item name="android:layout_marginTop">16dp</item> + <item name="android:layout_marginBottom">16dp</item> + <item name="android:scaleType">fitCenter</item> + </style> + + <style name="AccessibilityDialogIcon"> + <item name="android:layout_width">18dp</item> + <item name="android:layout_height">18dp</item> + <item name="android:layout_marginEnd">12dp</item> + <item name="android:scaleType">fitCenter</item> + </style> + + <style name="AccessibilityDialogTitle" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:gravity">center</item> + <item name="android:textSize">20sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + </style> + + <style name="AccessibilityDialogDescription" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginTop">16dp</item> + <item name="android:layout_marginBottom">32dp</item> + <item name="android:textSize">16sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + </style> + + <style name="AccessibilityDialogPermissionTitle" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textSize">16sp</item> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + </style> + + <style name="AccessibilityDialogPermissionDescription" + parent="@android:style/TextAppearance.DeviceDefault"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + + <style name="AccessibilityDialogButtonBarSpace"> + <item name="android:layout_width">0dp</item> + <item name="android:layout_height">0dp</item> + <item name="android:visibility">gone</item> + </style> + + <style name="AccessibilityDialogButtonList"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:orientation">vertical</item> + <item name="android:divider">@*android:drawable/list_divider_material</item> + <item name="android:showDividers">middle</item> + </style> + + <style name="AccessibilityDialogButton" + parent="@*android:style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">56dp</item> + <item name="android:paddingEnd">8dp</item> + <item name="android:paddingStart">8dp</item> + <item name="android:background">?android:attr/selectableItemBackground</item> + </style> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 16ad5c909575..f3aa936736ab 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3622,11 +3622,14 @@ <java-symbol type="string" name="accessibility_uncheck_legacy_item_warning" /> <java-symbol type="layout" name="accessibility_enable_service_warning" /> + <java-symbol type="layout" name="accessibility_service_warning" /> <java-symbol type="id" name="accessibility_permissionDialog_icon" /> <java-symbol type="id" name="accessibility_permissionDialog_title" /> <java-symbol type="id" name="accessibility_permission_enable_allow_button" /> <java-symbol type="id" name="accessibility_permission_enable_deny_button" /> + <java-symbol type="id" name="accessibility_permission_enable_uninstall_button" /> <java-symbol type="string" name="accessibility_enable_service_title" /> + <java-symbol type="string" name="accessibility_dialog_touch_filtered_warning" /> <java-symbol type="layout" name="accessibility_shortcut_chooser_item" /> <java-symbol type="id" name="accessibility_shortcut_target_checkbox" /> @@ -3655,6 +3658,7 @@ <java-symbol type="drawable" name="ic_accessibility_color_inversion" /> <java-symbol type="drawable" name="ic_accessibility_color_correction" /> + <java-symbol type="drawable" name="ic_accessibility_generic" /> <java-symbol type="drawable" name="ic_accessibility_hearing_aid" /> <java-symbol type="drawable" name="ic_accessibility_magnification" /> <java-symbol type="drawable" name="ic_accessibility_reduce_bright_colors" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 709646b00e5c..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -34,7 +34,7 @@ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ --> <!-- Arab Emirates --> - <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" /> + <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" /> <!-- Albania: 5 digits, known short codes listed --> <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> @@ -155,7 +155,7 @@ <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" /> <!-- Israel: 4 digits, known premium codes listed --> - <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477" /> + <shortcode country="il" pattern="\\d{4}" premium="4422|4545" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> @@ -198,9 +198,6 @@ <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf --> <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> - <!-- Namibia: 5 digits --> - <shortcode country="na" pattern="\\d{1,5}" free="40005" /> - <!-- The Netherlands, 4 digits, known premium codes listed, plus EU --> <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 20d8d91761e7..62d58b65e62a 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -165,6 +165,7 @@ <!-- AccessibilityShortcutChooserActivityTest permissions --> <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> + <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" /> <application android:theme="@style/Theme" diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 36e122301ba2..4b02257978d2 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -227,8 +227,7 @@ public class ActivityThreadTest { try { // Send process level config change. ClientTransaction transaction = newTransaction(activityThread); - transaction.addCallback(ConfigurationChangeItem.obtain( - new Configuration(newConfig), DEVICE_ID_INVALID)); + transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -245,7 +244,7 @@ public class ActivityThreadTest { newConfig.smallestScreenWidthDp++; transaction = newTransaction(activityThread); transaction.addCallback(ActivityConfigurationChangeItem.obtain( - activity.getActivityToken(), new Configuration(newConfig))); + activity.getActivityToken(), newConfig)); appThread.scheduleTransaction(transaction); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java index 4bbde0cd6366..723c0812468c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; +import android.annotation.NonNull; import android.app.ActivityOptions; import android.app.IApplicationThread; import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder; @@ -80,74 +81,29 @@ public class ObjectPoolTests { @Test public void testRecycleActivityConfigurationChangeItem() { - ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain( - null /* activityToken */, Configuration.EMPTY); - ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain( - mActivityToken, config()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityConfigurationChangeItem.obtain(mActivityToken, config())); } @Test public void testRecycleActivityResultItem() { - ActivityResultItem emptyItem = ActivityResultItem.obtain( - null /* activityToken */, null /* resultInfoList */); - ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityResultItem item2 = ActivityResultItem.obtain(mActivityToken, resultInfoList()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityResultItem.obtain(mActivityToken, resultInfoList())); } @Test public void testRecycleConfigurationChangeItem() { - ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0); - ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ConfigurationChangeItem.obtain(config(), 1)); } @Test public void testRecycleDestroyActivityItem() { - DestroyActivityItem emptyItem = DestroyActivityItem.obtain( - null /* activityToken */, false, 0); - DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true, 117); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - DestroyActivityItem item2 = DestroyActivityItem.obtain(mActivityToken, true, 14); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117)); } @Test public void testRecycleLaunchActivityItem() { final IBinder activityToken = new Binder(); final Intent intent = new Intent("action"); - int ident = 57; + final int ident = 57; final ActivityInfo activityInfo = new ActivityInfo(); activityInfo.flags = 42; activityInfo.setMaxAspectRatio(2.4f); @@ -158,20 +114,19 @@ public class ObjectPoolTests { final Configuration overrideConfig = new Configuration(); overrideConfig.assetsSeq = 5; final String referrer = "referrer"; - int procState = 4; + final int procState = 4; final Bundle bundle = new Bundle(); bundle.putString("key", "value"); final PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); final IBinder assistToken = new Binder(); final IBinder shareableActivityToken = new Binder(); - int deviceId = 3; + final int deviceId = 3; + final IBinder taskFragmentToken = new Binder(); - final Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder() - .setActivityToken(activityToken) - .setIntent(intent) + testRecycle(() -> new LaunchActivityItemBuilder( + activityToken, intent, activityInfo) .setIdent(ident) - .setInfo(activityInfo) .setCurConfig(config()) .setOverrideConfig(overrideConfig) .setReferrer(referrer) @@ -183,154 +138,74 @@ public class ObjectPoolTests { .setIsForward(true) .setAssistToken(assistToken) .setShareableActivityToken(shareableActivityToken) - .setTaskFragmentToken(new Binder()) + .setTaskFragmentToken(taskFragmentToken) .setDeviceId(deviceId) - .build(); - - LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build(); - LaunchActivityItem item = itemSupplier.get(); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - LaunchActivityItem item2 = itemSupplier.get(); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + .build()); } @Test public void testRecycleActivityRelaunchItem() { - ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain( - null /* activityToken */, null, null, 0, null, false); - Configuration overrideConfig = new Configuration(); - overrideConfig.assetsSeq = 5; - ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(), - referrerIntentList(), 42, mergedConfig(), true); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(), - referrerIntentList(), 42, mergedConfig(), true); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken, + resultInfoList(), referrerIntentList(), 42, mergedConfig(), true)); } @Test public void testRecycleMoveToDisplayItem() { - MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain( - null /* activityToken */, 0, Configuration.EMPTY); - MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4, config()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - MoveToDisplayItem item2 = MoveToDisplayItem.obtain(mActivityToken, 3, config()); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config())); } @Test public void testRecycleNewIntentItem() { - NewIntentItem emptyItem = NewIntentItem.obtain( - null /* activityToken */, null /* intents */, false /* resume */); - NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - NewIntentItem item2 = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> NewIntentItem.obtain(mActivityToken, referrerIntentList(), false)); } @Test public void testRecyclePauseActivityItemItem() { - PauseActivityItem emptyItem = PauseActivityItem.obtain( - null /* activityToken */, false, false, 0, false, false); - PauseActivityItem item = PauseActivityItem.obtain( - mActivityToken, true, true, 5, true, true); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - PauseActivityItem item2 = PauseActivityItem.obtain( - mActivityToken, true, false, 5, true, true); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true)); } @Test public void testRecycleResumeActivityItem() { - ResumeActivityItem emptyItem = ResumeActivityItem.obtain( - null /* activityToken */, false, false); - ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 3, true, false); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - ResumeActivityItem item2 = ResumeActivityItem.obtain(mActivityToken, 2, true, false); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> ResumeActivityItem.obtain(mActivityToken, 3, true, false)); } @Test public void testRecycleStartActivityItem() { - StartActivityItem emptyItem = StartActivityItem.obtain( - null /* activityToken */, null /* activityOptions */); - StartActivityItem item = StartActivityItem.obtain(mActivityToken, - ActivityOptions.makeBasic()); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - StartActivityItem item2 = StartActivityItem.obtain(mActivityToken, - ActivityOptions.makeBasic().setLaunchDisplayId(10)); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic())); } @Test public void testRecycleStopItem() { - StopActivityItem emptyItem = StopActivityItem.obtain(null /* activityToken */, 0); - StopActivityItem item = StopActivityItem.obtain(mActivityToken, 4); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); - - item.recycle(); - assertEquals(item, emptyItem); - - StopActivityItem item2 = StopActivityItem.obtain(mActivityToken, 3); - assertSame(item, item2); - assertNotEquals(item2, emptyItem); + testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4)); } @Test public void testRecycleClientTransaction() { - ClientTransaction emptyItem = ClientTransaction.obtain(null); - ClientTransaction item = ClientTransaction.obtain(mApplicationThread); - assertNotSame(item, emptyItem); - assertNotEquals(item, emptyItem); + testRecycle(() -> ClientTransaction.obtain(mApplicationThread)); + } + private void testRecycle(@NonNull Supplier<? extends ObjectPoolItem> obtain) { + // Reuse the same object after recycle. + final ObjectPoolItem item = obtain.get(); item.recycle(); - assertEquals(item, emptyItem); + final ObjectPoolItem item2 = obtain.get(); - ClientTransaction item2 = ClientTransaction.obtain(mApplicationThread); assertSame(item, item2); - assertNotEquals(item2, emptyItem); + + // Create new object when the pool is empty. + final ObjectPoolItem item3 = obtain.get(); + + assertNotSame(item, item3); + assertEquals(item, item3); + + // Reset fields after recycle. + item.recycle(); + + assertNotEquals(item, item3); + + // Recycled objects are equal. + item3.recycle(); + + assertEquals(item, item3); } } diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java index 5a88bad37d5f..c0e2a4993e1c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java +++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java @@ -18,6 +18,8 @@ package android.app.servertransaction; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; @@ -92,17 +94,18 @@ class TestUtils { } static class LaunchActivityItemBuilder { - @Nullable - private IBinder mActivityToken; - @Nullable - private Intent mIntent; + @NonNull + private final IBinder mActivityToken; + @NonNull + private final Intent mIntent; + @NonNull + private final ActivityInfo mInfo; + @NonNull + private final Configuration mCurConfig = new Configuration(); + @NonNull + private final Configuration mOverrideConfig = new Configuration(); + private int mIdent; - @Nullable - private ActivityInfo mInfo; - @Nullable - private Configuration mCurConfig; - @Nullable - private Configuration mOverrideConfig; private int mDeviceId; @Nullable private String mReferrer; @@ -130,16 +133,11 @@ class TestUtils { @Nullable private IBinder mTaskFragmentToken; - @NonNull - LaunchActivityItemBuilder setActivityToken(@Nullable IBinder activityToken) { - mActivityToken = activityToken; - return this; - } - - @NonNull - LaunchActivityItemBuilder setIntent(@Nullable Intent intent) { - mIntent = intent; - return this; + LaunchActivityItemBuilder(@NonNull IBinder activityToken, @NonNull Intent intent, + @NonNull ActivityInfo info) { + mActivityToken = requireNonNull(activityToken); + mIntent = requireNonNull(intent); + mInfo = requireNonNull(info); } @NonNull @@ -149,20 +147,14 @@ class TestUtils { } @NonNull - LaunchActivityItemBuilder setInfo(@Nullable ActivityInfo info) { - mInfo = info; - return this; - } - - @NonNull - LaunchActivityItemBuilder setCurConfig(@Nullable Configuration curConfig) { - mCurConfig = curConfig; + LaunchActivityItemBuilder setCurConfig(@NonNull Configuration curConfig) { + mCurConfig.setTo(curConfig); return this; } @NonNull - LaunchActivityItemBuilder setOverrideConfig(@Nullable Configuration overrideConfig) { - mOverrideConfig = overrideConfig; + LaunchActivityItemBuilder setOverrideConfig(@NonNull Configuration overrideConfig) { + mOverrideConfig.setTo(overrideConfig); return this; } diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java index f2b0f2e622b8..443dcb4e51c5 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java @@ -44,6 +44,8 @@ import android.app.ActivityThread.ActivityClientRecord; import android.app.ClientTransactionHandler; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -290,7 +292,7 @@ public class TransactionExecutorTests { // A previous queued launch transaction runs on main thread (execute). final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); final LaunchActivityItem launchItem = - spy(new LaunchActivityItemBuilder().setActivityToken(token).build()); + spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build()); launchTransaction.addCallback(launchItem); mExecutor.execute(launchTransaction); @@ -322,7 +324,7 @@ public class TransactionExecutorTests { // A previous queued launch transaction runs on main thread (execute). final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */); final LaunchActivityItem launchItem = - spy(new LaunchActivityItemBuilder().setActivityToken(token).build()); + spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build()); launchTransaction.addTransactionItem(launchItem); mExecutor.execute(launchTransaction); diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 4aa62c503a41..07921bfc34f5 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -173,11 +173,9 @@ public class TransactionParcelTests { final PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); - final LaunchActivityItem item = new LaunchActivityItemBuilder() - .setActivityToken(activityToken) - .setIntent(intent) + final LaunchActivityItem item = new LaunchActivityItemBuilder( + activityToken, intent, activityInfo) .setIdent(ident) - .setInfo(activityInfo) .setCurConfig(config()) .setOverrideConfig(overrideConfig) .setReferrer(referrer) diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java index c00eb91d752a..4d45daf3570c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -52,16 +52,18 @@ public class WindowStateResizeItemTest { private PendingTransactionActions mPendingActions; @Mock private IWindow mWindow; - @Mock + + private InsetsState mInsetsState; private ClientWindowFrames mFrames; - @Mock private MergedConfiguration mConfiguration; - @Mock - private InsetsState mInsetsState; @Before public void setup() { MockitoAnnotations.initMocks(this); + + mInsetsState = new InsetsState(); + mFrames = new ClientWindowFrames(); + mConfiguration = new MergedConfiguration(); } @Test diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 20ba4270e6fc..9b4dec4118a1 100644 --- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java +++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java @@ -182,4 +182,42 @@ public class PerformanceHintManagerTest { s.setPreferPowerEfficiency(true); s.setPreferPowerEfficiency(true); } + + @Test + public void testReportActualWorkDurationWithWorkDurationClass() { + Session s = createSession(); + assumeNotNull(s); + s.updateTargetWorkDuration(16); + s.reportActualWorkDuration(new WorkDuration(1, 12, 8, 6)); + s.reportActualWorkDuration(new WorkDuration(1, 33, 14, 20)); + s.reportActualWorkDuration(new WorkDuration(1, 14, 10, 6)); + } + + @Test + public void testReportActualWorkDurationWithWorkDurationClass_IllegalArgument() { + Session s = createSession(); + assumeNotNull(s); + s.updateTargetWorkDuration(16); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(-1, 12, 8, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(0, 12, 8, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(1, -1, 8, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(1, 0, 8, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(1, 12, -1, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(1, 12, 0, 6)); + }); + assertThrows(IllegalArgumentException.class, () -> { + s.reportActualWorkDuration(new WorkDuration(1, 12, 8, -1)); + }); + } } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index dd116b5b5c35..088b57feb200 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -44,14 +44,19 @@ import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.AlertDialog; import android.app.KeyguardManager; +import android.app.UiAutomation; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.graphics.Rect; +import android.os.Bundle; import android.os.Handler; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -62,6 +67,7 @@ import android.support.test.uiautomator.Until; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityManager; @@ -90,12 +96,17 @@ import java.util.Collections; @RunWith(AndroidJUnit4.class) public class AccessibilityShortcutChooserActivityTest { private static final String ONE_HANDED_MODE = "One-Handed mode"; + private static final String ALLOW_LABEL = "Allow"; private static final String DENY_LABEL = "Deny"; + private static final String UNINSTALL_LABEL = "Uninstall"; private static final String EDIT_LABEL = "Edit shortcuts"; private static final String LIST_TITLE_LABEL = "Choose features to use"; private static final String TEST_LABEL = "TEST_LABEL"; - private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class"); + private static final String TEST_PACKAGE = "TEST_LABEL"; + private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(TEST_PACKAGE, + "class"); private static final long UI_TIMEOUT_MS = 1000; + private UiAutomation mUiAutomation; private UiDevice mDevice; private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario; private TestAccessibilityShortcutChooserActivity mActivity; @@ -117,6 +128,10 @@ public class AccessibilityShortcutChooserActivityTest { private IAccessibilityManager mAccessibilityManagerService; @Mock private KeyguardManager mKeyguardManager; + @Mock + private PackageManager mPackageManager; + @Mock + private PackageInstaller mPackageInstaller; @Before public void setUp() throws Exception { @@ -125,6 +140,7 @@ public class AccessibilityShortcutChooserActivityTest { assumeFalse("AccessibilityShortcutChooserActivity not supported on watch", pm.hasSystemFeature(PackageManager.FEATURE_WATCH)); + mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); mDevice.wakeUp(); when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo); @@ -134,12 +150,15 @@ public class AccessibilityShortcutChooserActivityTest { when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME); when(mAccessibilityManagerService.getInstalledAccessibilityServiceList( anyInt())).thenReturn(new ParceledListSlice<>( - Collections.singletonList(mAccessibilityServiceInfo))); + Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller); + TestAccessibilityShortcutChooserActivity.setupForTesting( - mAccessibilityManagerService, mKeyguardManager); + mAccessibilityManagerService, mKeyguardManager, + mPackageManager); } @After @@ -150,18 +169,12 @@ public class AccessibilityShortcutChooserActivityTest { } @Test - public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() { + @RequiresFlagsDisabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) + public void selectTestService_oldPermissionDialog_deny_dialogIsHidden() { launchActivity(); openShortcutsList(); - // Performing the double-click is flaky so retry if needed. - for (int attempt = 1; attempt <= 2; attempt++) { - onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick()); - if (mDevice.wait(Until.hasObject(By.text(DENY_LABEL)), UI_TIMEOUT_MS)) { - break; - } - } - + mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS); onView(withText(DENY_LABEL)).perform(scrollTo(), click()); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); @@ -170,6 +183,50 @@ public class AccessibilityShortcutChooserActivityTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) + public void selectTestService_permissionDialog_allow_rowChecked() { + launchActivity(); + openShortcutsList(); + + mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS); + clickSystemDialogButton(ALLOW_LABEL); + + assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), + UI_TIMEOUT_MS)).isTrue(); + assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isTrue(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) + public void selectTestService_permissionDialog_deny_rowNotChecked() { + launchActivity(); + openShortcutsList(); + + mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS); + clickSystemDialogButton(DENY_LABEL); + + assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), + UI_TIMEOUT_MS)).isTrue(); + assertThat(mDevice.wait(Until.hasObject(By.checked(true)), UI_TIMEOUT_MS)).isFalse(); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) + public void selectTestService_permissionDialog_uninstall_callsUninstaller_rowRemoved() { + launchActivity(); + openShortcutsList(); + + mDevice.findObject(By.text(TEST_LABEL)).clickAndWait(Until.newWindow(), UI_TIMEOUT_MS); + clickSystemDialogButton(UNINSTALL_LABEL); + + verify(mPackageInstaller).uninstall(eq(TEST_PACKAGE), any()); + assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), + UI_TIMEOUT_MS)).isTrue(); + assertThat(mDevice.wait(Until.hasObject(By.textStartsWith(TEST_LABEL)), + UI_TIMEOUT_MS)).isFalse(); + } + + @Test public void clickServiceTarget_notPermittedByAdmin_sendRestrictedDialogIntent() throws Exception { when(mAccessibilityManagerService.isAccessibilityTargetAllowed( @@ -239,6 +296,18 @@ public class AccessibilityShortcutChooserActivityTest { mDevice.wait(Until.hasObject(By.textStartsWith(LIST_TITLE_LABEL)), UI_TIMEOUT_MS); } + private void clickSystemDialogButton(String dialogButtonText) { + // Use UiAutomation to find the button because UiDevice struggles to find + // a UI element in a system dialog. + final AccessibilityNodeInfo button = + mUiAutomation.getRootInActiveWindow() + .findAccessibilityNodeInfosByText(dialogButtonText).stream() + .filter(AccessibilityNodeInfo::isClickable).findFirst().get(); + final Rect bounds = new Rect(); + button.getBoundsInScreen(bounds); + mDevice.click(bounds.centerX(), bounds.centerY()); + } + /** * Used for testing. */ @@ -246,12 +315,30 @@ public class AccessibilityShortcutChooserActivityTest { AccessibilityShortcutChooserActivity { private static IAccessibilityManager sAccessibilityManagerService; private static KeyguardManager sKeyguardManager; + private static PackageManager sPackageManager; public static void setupForTesting( IAccessibilityManager accessibilityManagerService, - KeyguardManager keyguardManager) { + KeyguardManager keyguardManager, + PackageManager packageManager) { sAccessibilityManagerService = accessibilityManagerService; sKeyguardManager = keyguardManager; + sPackageManager = packageManager; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Flags.deduplicateAccessibilityWarningDialog()) { + // Setting the Theme is necessary here for the dialog to use the proper style + // resources as designated in its layout XML. + setTheme(R.style.Theme_DeviceDefault_DayNight); + } + } + + @Override + public PackageManager getPackageManager() { + return sPackageManager; } @Override diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java new file mode 100644 index 000000000000..b76dd51d3f2b --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityServiceWarningTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.accessibility.dialog; + +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; + +import static com.google.common.truth.Truth.assertThat; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.AlertDialog; +import android.content.Context; +import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.InputDevice; +import android.view.MotionEvent; +import android.view.View; +import android.view.Window; +import android.widget.TextView; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.R; +import com.android.internal.accessibility.TestUtils; + +import com.google.common.truth.Expect; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Unit Tests for + * {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning} + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@RequiresFlagsEnabled( + android.view.accessibility.Flags.FLAG_DEDUPLICATE_ACCESSIBILITY_WARNING_DIALOG) +public class AccessibilityServiceWarningTest { + private static final String A11Y_SERVICE_PACKAGE_LABEL = "TestA11yService"; + private static final String A11Y_SERVICE_SUMMARY = "TestA11yService summary"; + private static final String A11Y_SERVICE_COMPONENT_NAME = + "fake.package/test.a11yservice.name"; + + private Context mContext; + private AccessibilityServiceInfo mAccessibilityServiceInfo; + private AtomicBoolean mAllowListener; + private AtomicBoolean mDenyListener; + private AtomicBoolean mUninstallListener; + + @Rule + public final Expect expect = Expect.create(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() throws RemoteException { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mAccessibilityServiceInfo = TestUtils.createFakeServiceInfo( + A11Y_SERVICE_PACKAGE_LABEL, + A11Y_SERVICE_SUMMARY, + A11Y_SERVICE_COMPONENT_NAME, + /* isAlwaysOnService*/ false); + mAllowListener = new AtomicBoolean(false); + mDenyListener = new AtomicBoolean(false); + mUninstallListener = new AtomicBoolean(false); + } + + @Test + public void createAccessibilityServiceWarningDialog_hasExpectedWindowParams() { + final AlertDialog dialog = + AccessibilityServiceWarning.createAccessibilityServiceWarningDialog( + mContext, + mAccessibilityServiceInfo, + null, null, null); + final Window dialogWindow = dialog.getWindow(); + assertThat(dialogWindow).isNotNull(); + + expect.that(dialogWindow.getAttributes().privateFlags + & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS).isEqualTo( + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); + expect.that(dialogWindow.getAttributes().type).isEqualTo(TYPE_SYSTEM_DIALOG); + } + + @Test + public void createAccessibilityServiceWarningDialog_hasExpectedServiceName() { + final TextView title = createDialogContentView().findViewById( + R.id.accessibility_permissionDialog_title); + assertThat(title).isNotNull(); + + assertThat(title.getText().toString()).contains(A11Y_SERVICE_PACKAGE_LABEL); + } + + @Test + public void createAccessibilityServiceWarningDialog_clickAllow() { + final View allowButton = createDialogContentView().findViewById( + R.id.accessibility_permission_enable_allow_button); + assertThat(allowButton).isNotNull(); + + allowButton.performClick(); + + expect.that(mAllowListener.get()).isTrue(); + expect.that(mDenyListener.get()).isFalse(); + expect.that(mUninstallListener.get()).isFalse(); + } + + @Test + public void createAccessibilityServiceWarningDialog_clickDeny() { + final View denyButton = createDialogContentView().findViewById( + R.id.accessibility_permission_enable_deny_button); + assertThat(denyButton).isNotNull(); + + denyButton.performClick(); + + expect.that(mAllowListener.get()).isFalse(); + expect.that(mDenyListener.get()).isTrue(); + expect.that(mUninstallListener.get()).isFalse(); + } + + @Test + public void createAccessibilityServiceWarningDialog_clickUninstall() { + final View uninstallButton = createDialogContentView().findViewById( + R.id.accessibility_permission_enable_uninstall_button); + assertThat(uninstallButton).isNotNull(); + + uninstallButton.performClick(); + + expect.that(mAllowListener.get()).isFalse(); + expect.that(mDenyListener.get()).isFalse(); + expect.that(mUninstallListener.get()).isTrue(); + } + + @Test + public void getTouchConsumingListener() { + final View allowButton = createDialogContentView().findViewById( + R.id.accessibility_permission_enable_allow_button); + assertThat(allowButton).isNotNull(); + final View.OnTouchListener listener = + AccessibilityServiceWarning.getTouchConsumingListener(); + + expect.that(listener.onTouch(allowButton, createMotionEvent(0))).isFalse(); + expect.that(listener.onTouch(allowButton, + createMotionEvent(MotionEvent.FLAG_WINDOW_IS_OBSCURED))).isTrue(); + expect.that(listener.onTouch(allowButton, + createMotionEvent(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED))).isTrue(); + } + + private View createDialogContentView() { + return AccessibilityServiceWarning.createAccessibilityServiceWarningDialogContentView( + mContext, + mAccessibilityServiceInfo, + (v) -> mAllowListener.set(true), + (v) -> mDenyListener.set(true), + (v) -> mUninstallListener.set(true)); + } + + private MotionEvent createMotionEvent(int flags) { + MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[]{ + new MotionEvent.PointerProperties() + }; + MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[]{ + new MotionEvent.PointerCoords() + }; + return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 1, props, coords, + 0, 0, 0, 0, -1, 0, InputDevice.SOURCE_TOUCHSCREEN, flags); + } +} diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java index 3e3c77b7b21c..03c38cc137ad 100644 --- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java +++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java @@ -25,9 +25,11 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import android.annotation.EnforcePermission; import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback; import android.os.IBinder; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import androidx.test.filters.SmallTest; @@ -57,7 +59,8 @@ public final class DeviceStateManagerGlobalTest { @Before public void setUp() { - mService = new TestDeviceStateManagerService(); + FakePermissionEnforcer permissionEnforcer = new FakePermissionEnforcer(); + mService = new TestDeviceStateManagerService(permissionEnforcer); mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService); assertFalse(mService.mCallbacks.isEmpty()); } @@ -261,6 +264,10 @@ public final class DeviceStateManagerGlobalTest { private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>(); + TestDeviceStateManagerService(FakePermissionEnforcer enforcer) { + super(enforcer); + } + private DeviceStateInfo getInfo() { final int mergedBaseState = mBaseStateRequest == null ? mBaseState : mBaseStateRequest.state; @@ -380,7 +387,10 @@ public final class DeviceStateManagerGlobalTest { // No-op in the test since DeviceStateManagerGlobal just calls into the system server with // no business logic around it. @Override - public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {} + @EnforcePermission(android.Manifest.permission.CONTROL_DEVICE_STATE) + public void onStateRequestOverlayDismissed(boolean shouldCancelMode) { + onStateRequestOverlayDismissed_enforcePermission(); + } public void setSupportedStates(int[] states) { mSupportedStates = states; diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index dc2b0561957d..b06561101071 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -415,12 +415,6 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, - "-1717147904": { - "message": "Current focused window is embeddedWindow. Dispatch KEYCODE_BACK.", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "-1710206702": { "message": "Display id=%d is frozen while keyguard locked, return %d", "level": "VERBOSE", @@ -1213,12 +1207,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "-997565097": { - "message": "Focused window found using getFocusedWindowToken", - "level": "DEBUG", - "group": "WM_DEBUG_BACK_PREVIEW", - "at": "com\/android\/server\/wm\/BackNavigationController.java" - }, "-993378225": { "message": "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", "level": "VERBOSE", @@ -2233,6 +2221,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "-98422345": { + "message": "Focus window is closing.", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-91393839": { "message": "Set animatingExit: reason=remove\/applyAnimation win=%s", "level": "VERBOSE", @@ -2731,12 +2725,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "309039362": { - "message": "SURFACE MATRIX [%f,%f,%f,%f]: %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "312030608": { "message": "New topFocusedDisplayId=%d", "level": "DEBUG", @@ -3091,12 +3079,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "633654009": { - "message": "SURFACE POS (setPositionInTransaction) @ (%f,%f): %s", - "level": "INFO", - "group": "WM_SHOW_TRANSACTIONS", - "at": "com\/android\/server\/wm\/WindowSurfaceController.java" - }, "638429464": { "message": "\tRemove container=%s", "level": "DEBUG", diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index 66fabec91924..a4bce9eb5e88 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -23,6 +23,8 @@ import android.annotation.NonNull; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.nio.Buffer; import java.nio.ShortBuffer; @@ -43,6 +45,7 @@ public class Mesh { * Determines how the mesh is represented and will be drawn. */ @IntDef({TRIANGLES, TRIANGLE_STRIP}) + @Retention(RetentionPolicy.SOURCE) private @interface Mode {} /** diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java index 5509f000aca5..45e29a88c7db 100644 --- a/graphics/java/android/graphics/drawable/Icon.java +++ b/graphics/java/android/graphics/drawable/Icon.java @@ -62,6 +62,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; @@ -116,6 +118,7 @@ public final class Icon implements Parcelable { */ @IntDef({TYPE_BITMAP, TYPE_RESOURCE, TYPE_DATA, TYPE_URI, TYPE_ADAPTIVE_BITMAP, TYPE_URI_ADAPTIVE_BITMAP}) + @Retention(RetentionPolicy.SOURCE) public @interface IconType { } diff --git a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml index 5c8c84cbb85b..ed00a87b0fa5 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml @@ -21,8 +21,8 @@ android:layout_width="wrap_content" android:paddingTop="48dp" android:paddingBottom="48dp" - android:paddingEnd="@dimen/bubble_user_education_padding_end" - android:layout_marginEnd="@dimen/bubble_user_education_margin_end" + android:paddingHorizontal="@dimen/bubble_user_education_padding_horizontal" + android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal" android:orientation="vertical" android:background="@drawable/bubble_stack_user_education_bg" > diff --git a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml index b28f58f8356d..4f6bdfd98d54 100644 --- a/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml +++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml @@ -23,8 +23,8 @@ android:clickable="true" android:paddingTop="28dp" android:paddingBottom="16dp" - android:paddingEnd="@dimen/bubble_user_education_padding_end" - android:layout_marginEnd="@dimen/bubble_user_education_margin_end" + android:paddingEnd="@dimen/bubble_user_education_padding_horizontal" + android:layout_marginEnd="@dimen/bubble_user_education_margin_horizontal" android:orientation="vertical" android:background="@drawable/bubble_stack_user_education_bg" > diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index de9d2a292540..f20d44df21b1 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -224,9 +224,9 @@ <dimen name="bubbles_user_education_width">480dp</dimen> <!-- Margin applied to the end of the user education views (really only matters for phone since the width is match parent). --> - <dimen name="bubble_user_education_margin_end">24dp</dimen> + <dimen name="bubble_user_education_margin_horizontal">24dp</dimen> <!-- Padding applied to the end of the user education view. --> - <dimen name="bubble_user_education_padding_end">58dp</dimen> + <dimen name="bubble_user_education_padding_horizontal">58dp</dimen> <!-- Padding between the bubble and the user education text. --> <dimen name="bubble_user_education_stack_padding">16dp</dimen> <!-- Max width for the bubble popup view. --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 144c456f8838..09ae84a50328 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -46,6 +46,12 @@ public class BubblePositioner { ? "BubblePositioner" : BubbleDebugConfig.TAG_BUBBLES; + /** The screen edge the bubble stack is pinned to */ + public enum StackPinnedEdge { + LEFT, + RIGHT + } + /** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/ public static final int NUM_VISIBLE_WHEN_RESTING = 2; /** Indicates a bubble's height should be the maximum available space. **/ @@ -694,6 +700,15 @@ public class BubblePositioner { final boolean startOnLeft = isAppBubble ? layoutDirection == LAYOUT_DIRECTION_RTL : layoutDirection != LAYOUT_DIRECTION_RTL; + return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT); + } + + /** + * The stack position to use if user education is being shown. + * + * @param stackPinnedEdge the screen edge the stack is pinned to. + */ + public PointF getStartPosition(StackPinnedEdge stackPinnedEdge) { final RectF allowableStackPositionRegion = getAllowableStackPositionRegion( 1 /* default starts with 1 bubble */); if (isLargeScreen()) { @@ -702,7 +717,7 @@ public class BubblePositioner { final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f); final float offset = desiredY / mScreenRect.height(); return new BubbleStackView.RelativeStackPosition( - startOnLeft, + stackPinnedEdge == StackPinnedEdge.LEFT, offset) .getAbsolutePositionInRegion(allowableStackPositionRegion); } else { @@ -710,7 +725,7 @@ public class BubblePositioner { R.dimen.bubble_stack_starting_offset_y); // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge return new BubbleStackView.RelativeStackPosition( - startOnLeft, + stackPinnedEdge == StackPinnedEdge.LEFT, startingVerticalOffset / mPositionRect.height()) .getAbsolutePositionInRegion(allowableStackPositionRegion); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 87461dcb0515..2cee675e83be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -25,6 +25,8 @@ import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_ import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING; +import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.LEFT; +import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT; import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; @@ -1296,6 +1298,9 @@ public class BubbleStackView extends FrameLayout return shouldShow; } + /** + * Show manage education if should show and was not showing before. + */ private void maybeShowManageEdu() { if (!shouldShowManageEdu()) { return; @@ -1304,7 +1309,16 @@ public class BubbleStackView extends FrameLayout mManageEduView = new ManageEducationView(mContext, mPositioner); addView(mManageEduView); } - mManageEduView.show(mExpandedBubble.getExpandedView()); + showManageEdu(); + } + + /** + * Show manage education if was not showing before. + */ + private void showManageEdu() { + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) return; + mManageEduView.show(mExpandedBubble.getExpandedView(), + mStackAnimationController.isStackOnLeftSide()); } @VisibleForTesting @@ -1350,10 +1364,21 @@ public class BubbleStackView extends FrameLayout mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); } + return showStackEdu(); + } + + /** + * @return true if education view for the collapsed stack was not showing before. + */ + private boolean showStackEdu() { + // Stack appears on top of the education views mBubbleContainer.bringToFront(); // Ensure the stack is in the correct spot - mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition()); - return mStackEduView.show(mPositioner.getDefaultStartPosition()); + PointF position = mPositioner.getStartPosition( + mStackAnimationController.isStackOnLeftSide() ? LEFT : RIGHT); + // Animate stack to the position + mStackAnimationController.springStackAfterFling(position.x, position.y); + return mStackEduView.show(position); } @VisibleForTesting @@ -1367,16 +1392,13 @@ public class BubbleStackView extends FrameLayout removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); - mBubbleContainer.bringToFront(); // Stack appears on top of the stack education - // Ensure the stack is in the correct spot - mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition()); - mStackEduView.show(mPositioner.getDefaultStartPosition()); + showStackEdu(); } if (isManageEduVisible()) { removeView(mManageEduView); mManageEduView = new ManageEducationView(mContext, mPositioner); addView(mManageEduView); - mManageEduView.show(mExpandedBubble.getExpandedView()); + showManageEdu(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt index 1b41f793311d..61e17c8ec459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt @@ -19,6 +19,7 @@ import android.content.Context import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.ColorDrawable +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -32,10 +33,10 @@ import com.android.wm.shell.animation.Interpolators * User education view to highlight the manage button that allows a user to configure the settings * for the bubble. Shown only the first time a user expands a bubble. */ -class ManageEducationView constructor(context: Context, positioner: BubblePositioner) - : LinearLayout(context) { +class ManageEducationView(context: Context, positioner: BubblePositioner) : LinearLayout(context) { - private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView" + private val TAG = + if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "ManageEducationView" else BubbleDebugConfig.TAG_BUBBLES private val ANIMATE_DURATION: Long = 200 @@ -62,7 +63,7 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi override fun setLayoutDirection(layoutDirection: Int) { super.setLayoutDirection(layoutDirection) - setDrawableDirection() + setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR) } override fun onFinishInflate() { @@ -71,8 +72,10 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi } private fun setButtonColor() { - val typedArray = mContext.obtainStyledAttributes(intArrayOf( - com.android.internal.R.attr.colorAccentPrimary)) + val typedArray = + mContext.obtainStyledAttributes( + intArrayOf(com.android.internal.R.attr.colorAccentPrimary) + ) val buttonColor = typedArray.getColor(0 /* index */, Color.TRANSPARENT) typedArray.recycle() @@ -81,11 +84,11 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi gotItButton.setBackgroundDrawable(ColorDrawable(buttonColor)) } - private fun setDrawableDirection() { + private fun setDrawableDirection(isOnLeft: Boolean) { manageView.setBackgroundResource( - if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) - R.drawable.bubble_stack_user_education_bg_rtl - else R.drawable.bubble_stack_user_education_bg) + if (isOnLeft) R.drawable.bubble_stack_user_education_bg + else R.drawable.bubble_stack_user_education_bg_rtl + ) } /** @@ -93,48 +96,31 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi * bubble stack is expanded for the first time. * * @param expandedView the expandedView the user education is shown on top of. + * @param isStackOnLeft the bubble stack position on the screen */ - fun show(expandedView: BubbleExpandedView) { + fun show(expandedView: BubbleExpandedView, isStackOnLeft: Boolean) { setButtonColor() if (visibility == VISIBLE) return bubbleExpandedView = expandedView expandedView.taskView?.setObscuredTouchRect(Rect(positioner.screenRect)) - layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape) - context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) - else ViewGroup.LayoutParams.MATCH_PARENT - alpha = 0f visibility = View.VISIBLE expandedView.getManageButtonBoundsOnScreen(realManageButtonRect) - val isRTL = mContext.resources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL - if (isRTL) { - val rightPadding = positioner.screenRect.right - realManageButtonRect.right - - expandedView.manageButtonMargin - manageView.setPadding(manageView.paddingLeft, manageView.paddingTop, - rightPadding, manageView.paddingBottom) - } else { - manageView.setPadding(realManageButtonRect.left - expandedView.manageButtonMargin, - manageView.paddingTop, manageView.paddingRight, manageView.paddingBottom) - } + layoutManageView(realManageButtonRect, expandedView.manageButtonMargin, isStackOnLeft) + post { - manageButton - .setOnClickListener { - hide() - expandedView.requireViewById<View>(R.id.manage_button).performClick() - } + manageButton.setOnClickListener { + hide() + expandedView.requireViewById<View>(R.id.manage_button).performClick() + } gotItButton.setOnClickListener { hide() } setOnClickListener { hide() } val offsetViewBounds = Rect() manageButton.getDrawingRect(offsetViewBounds) manageView.offsetDescendantRectToMyCoords(manageButton, offsetViewBounds) - if (isRTL && (positioner.isLargeScreen || positioner.isLandscape)) { - translationX = (positioner.screenRect.right - width).toFloat() - } else { - translationX = 0f - } translationY = (realManageButtonRect.top - offsetViewBounds.top).toFloat() bringToFront() animate() @@ -145,6 +131,79 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi setShouldShow(false) } + /** + * On tablet the user education is aligned to the left or to right side depending on where the + * stack is positioned when collapsed. On phone the user education follows the layout direction. + * + * @param manageButtonRect the manage button rect on the screen + * @param manageButtonMargin the manage button margin + * @param isStackOnLeft the bubble stack position on the screen + */ + private fun layoutManageView( + manageButtonRect: Rect, + manageButtonMargin: Int, + isStackOnLeft: Boolean + ) { + val isLTR = resources.configuration.layoutDirection == LAYOUT_DIRECTION_LTR + val isPinnedLeft = if (positioner.isLargeScreen) isStackOnLeft else isLTR + val paddingHorizontal = + resources.getDimensionPixelSize(R.dimen.bubble_user_education_padding_horizontal) + + // The user education view background image direction + setDrawableDirection(isPinnedLeft) + + // The user education view layout gravity + gravity = if (isPinnedLeft) Gravity.LEFT else Gravity.RIGHT + + // The user education view width + manageView.layoutParams.width = + when { + // Left-to-Right direction and the education is on the right side + isLTR && !isPinnedLeft -> + positioner.screenRect.right - + (manageButtonRect.left - manageButtonMargin - paddingHorizontal) + // Right-to-Left direction and the education is on the left side + !isLTR && isPinnedLeft -> + manageButtonRect.right + manageButtonMargin + paddingHorizontal + // Large screen and the education position matches the layout direction + positioner.isLargeScreen -> ViewGroup.LayoutParams.WRAP_CONTENT + // Small screen, landscape orientation + positioner.isLandscape -> + resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) + // Otherwise + else -> ViewGroup.LayoutParams.MATCH_PARENT + } + + // The user education view margin on the opposite side of where it's pinned + (manageView.layoutParams as MarginLayoutParams).apply { + val edgeMargin = + resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal) + leftMargin = if (isPinnedLeft) 0 else edgeMargin + rightMargin = if (isPinnedLeft) edgeMargin else 0 + } + + // The user education view padding + manageView.apply { + val paddingLeft = + if (isLTR && isPinnedLeft) { + // Offset on the left to align with the manage button + manageButtonRect.left - manageButtonMargin + } else { + // Use default padding + paddingHorizontal + } + val paddingRight = + if (!isLTR && !isPinnedLeft) { + // Offset on the right to align with the manage button + positioner.screenRect.right - manageButtonRect.right - manageButtonMargin + } else { + // Use default padding + paddingHorizontal + } + setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom) + } + } + fun hide() { bubbleExpandedView?.taskView?.setObscuredTouchRect(null) if (visibility != VISIBLE || isHiding) return @@ -160,9 +219,12 @@ class ManageEducationView constructor(context: Context, positioner: BubblePositi } private fun setShouldShow(shouldShow: Boolean) { - context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) - .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply() + context + .getSharedPreferences(context.packageName, Context.MODE_PRIVATE) + .edit() + .putBoolean(PREF_MANAGED_EDUCATION, !shouldShow) + .apply() } } -const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding"
\ No newline at end of file +const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index 5e3a077a3716..2cabb65abe7a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -21,7 +21,6 @@ import android.graphics.PointF import android.view.KeyEvent import android.view.LayoutInflater import android.view.View -import android.view.View.OnKeyListener import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView @@ -30,16 +29,17 @@ import com.android.wm.shell.R import com.android.wm.shell.animation.Interpolators /** - * User education view to highlight the collapsed stack of bubbles. - * Shown only the first time a user taps the stack. + * User education view to highlight the collapsed stack of bubbles. Shown only the first time a user + * taps the stack. */ -class StackEducationView constructor( +class StackEducationView( context: Context, positioner: BubblePositioner, controller: BubbleController ) : LinearLayout(context) { - private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView" + private val TAG = + if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView" else BubbleDebugConfig.TAG_BUBBLES private val ANIMATE_DURATION: Long = 200 @@ -69,7 +69,7 @@ class StackEducationView constructor( override fun setLayoutDirection(layoutDirection: Int) { super.setLayoutDirection(layoutDirection) - setDrawableDirection() + setDrawableDirection(layoutDirection == LAYOUT_DIRECTION_LTR) } override fun onFinishInflate() { @@ -111,16 +111,16 @@ class StackEducationView constructor( descTextView.setTextColor(textColor) } - private fun setDrawableDirection() { + private fun setDrawableDirection(isOnLeft: Boolean) { view.setBackgroundResource( - if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) - R.drawable.bubble_stack_user_education_bg - else R.drawable.bubble_stack_user_education_bg_rtl) + if (isOnLeft) R.drawable.bubble_stack_user_education_bg + else R.drawable.bubble_stack_user_education_bg_rtl + ) } /** - * If necessary, shows the user education view for the bubble stack. This appears the first - * time a user taps on a bubble. + * If necessary, shows the user education view for the bubble stack. This appears the first time + * a user taps on a bubble. * * @return true if user education was shown and wasn't showing before, false otherwise. */ @@ -129,29 +129,44 @@ class StackEducationView constructor( if (visibility == VISIBLE) return false controller.updateWindowFlagsForBackpress(true /* interceptBack */) - layoutParams.width = if (positioner.isLargeScreen || positioner.isLandscape) - context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) - else ViewGroup.LayoutParams.MATCH_PARENT + layoutParams.width = + if (positioner.isLargeScreen || positioner.isLandscape) + context.resources.getDimensionPixelSize(R.dimen.bubbles_user_education_width) + else ViewGroup.LayoutParams.MATCH_PARENT + + val isStackOnLeft = positioner.isStackOnLeft(stackPosition) + (view.layoutParams as MarginLayoutParams).apply { + // Update the horizontal margins depending on the stack position + val edgeMargin = + resources.getDimensionPixelSize(R.dimen.bubble_user_education_margin_horizontal) + leftMargin = if (isStackOnLeft) 0 else edgeMargin + rightMargin = if (isStackOnLeft) edgeMargin else 0 + } - val stackPadding = context.resources.getDimensionPixelSize( - R.dimen.bubble_user_education_stack_padding) + val stackPadding = + context.resources.getDimensionPixelSize(R.dimen.bubble_user_education_stack_padding) setAlpha(0f) setVisibility(View.VISIBLE) + setDrawableDirection(isOnLeft = isStackOnLeft) post { requestFocus() with(view) { - if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR) { - setPadding(positioner.bubbleSize + stackPadding, paddingTop, paddingRight, - paddingBottom) + if (isStackOnLeft) { + setPadding( + positioner.bubbleSize + stackPadding, + paddingTop, + paddingRight, + paddingBottom + ) + translationX = 0f } else { - setPadding(paddingLeft, paddingTop, positioner.bubbleSize + stackPadding, - paddingBottom) - if (positioner.isLargeScreen || positioner.isLandscape) { - translationX = (positioner.screenRect.right - width - stackPadding) - .toFloat() - } else { - translationX = 0f - } + setPadding( + paddingLeft, + paddingTop, + positioner.bubbleSize + stackPadding, + paddingBottom + ) + translationX = (positioner.screenRect.right - width - stackPadding).toFloat() } translationY = stackPosition.y + positioner.bubbleSize / 2 - getHeight() / 2 } @@ -168,7 +183,7 @@ class StackEducationView constructor( * If necessary, hides the stack education view. * * @param isExpanding if true this indicates the hide is happening due to the bubble being - * expanded, false if due to a touch outside of the bubble stack. + * expanded, false if due to a touch outside of the bubble stack. */ fun hide(isExpanding: Boolean) { if (visibility != VISIBLE || isHiding) return @@ -182,9 +197,12 @@ class StackEducationView constructor( } private fun setShouldShow(shouldShow: Boolean) { - context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) - .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply() + context + .getSharedPreferences(context.packageName, Context.MODE_PRIVATE) + .edit() + .putBoolean(PREF_STACK_EDUCATION, !shouldShow) + .apply() } } -const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding"
\ No newline at end of file +const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index b26d0613f3b6..07c54293111c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -16,6 +16,7 @@ package com.android.wm.shell.unfold; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; @@ -188,23 +189,27 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull TransitionFinishCallback finishCallback) { - if (info.getType() == TRANSIT_CHANGE) { - // TODO (b/286928742) unfold transition handler should be part of mixed handler to - // handle merges better. - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null - && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { - // Tasks that are always on top (e.g. bubbles), will handle their own transition - // as they are on top of everything else. So skip merging transitions here. - return; - } + if (info.getType() != TRANSIT_CHANGE) { + return; + } + if ((info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { + return; + } + // TODO (b/286928742) unfold transition handler should be part of mixed handler to + // handle merges better. + for (int i = 0; i < info.getChanges().size(); ++i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { + // Tasks that are always on top (e.g. bubbles), will handle their own transition + // as they are on top of everything else. So skip merging transitions here. + return; } - // Apply changes happening during the unfold animation immediately - t.apply(); - finishCallback.onTransitionFinished(null); } + // Apply changes happening during the unfold animation immediately + t.apply(); + finishCallback.onTransitionFinished(null); } /** Whether `request` contains an unfold action. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 7c6fb99e9c8b..518f4b87e197 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -120,7 +120,7 @@ class DragResizeInputListener implements AutoCloseable { mWindowSession.grantInputChannel( mDisplayId, mDecorationSurface, - mFakeWindow, + mFakeWindow.asBinder(), null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, @@ -155,7 +155,7 @@ class DragResizeInputListener implements AutoCloseable { mWindowSession.grantInputChannel( mDisplayId, mInputSinkSurface, - mFakeSinkWindow, + mFakeSinkWindow.asBinder(), null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index 7917ba6eeb41..6d73c12dc304 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_NONE; import static com.google.common.truth.Truth.assertThat; @@ -46,6 +47,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.TransitionInfoBuilder; import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; @@ -265,6 +267,42 @@ public class UnfoldTransitionHandlerTest { verify(finishCallback).onTransitionFinished(any()); } + @Test + public void mergeAnimation_eatsDisplayOnlyTransitions() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); + TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); + TransitionFinishCallback mergeCallback = mock(TransitionFinishCallback.class); + + mUnfoldTransitionHandler.startAnimation( + mTransition, + mock(TransitionInfo.class), + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + finishCallback); + + // Offer a keyguard unlock transition - this should NOT merge + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE, TRANSIT_FLAG_KEYGUARD_GOING_AWAY).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(finishCallback, never()).onTransitionFinished(any()); + + // Offer a CHANGE-only transition - this SHOULD merge (b/278064943) + mUnfoldTransitionHandler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_CHANGE).build(), + mock(SurfaceControl.Transaction.class), + mTransition, + mergeCallback); + verify(mergeCallback).onTransitionFinished(any()); + + // We should never have finished the original transition. + verify(finishCallback, never()).onTransitionFinished(any()); + } + private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( @@ -372,4 +410,4 @@ public class UnfoldTransitionHandlerTest { private TransitionInfo createNonUnfoldTransitionInfo() { return new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); } -}
\ No newline at end of file +} diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index a1b05c186ec0..a7d64231da80 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -597,7 +597,13 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject, SkIRect clipBounds; if (enableClip) { uirenderer::Rect initialClipBounds; - props.getClippingRectForFlags(props.getClippingFlags(), &initialClipBounds); + const auto clipFlags = props.getClippingFlags(); + if (clipFlags) { + props.getClippingRectForFlags(clipFlags, &initialClipBounds); + } else { + // Works for RenderNode::damageSelf() + initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } clipBounds = info.damageAccumulator ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform) diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index e706eb083ab6..d55d28d469d0 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -527,52 +527,65 @@ Frame VulkanManager::dequeueNextBuffer(VulkanSurface* surface) { return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge); } -struct DestroySemaphoreInfo { +class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> { PFN_vkDestroySemaphore mDestroyFunction; VkDevice mDevice; VkSemaphore mSemaphore; + GrBackendSemaphore mGrBackendSemaphore; - DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, - VkSemaphore semaphore) - : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {} + SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, + VkSemaphore semaphore) + : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) { + mGrBackendSemaphore.initVulkan(semaphore); + } + + ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); } + + friend class LightRefBase<SharedSemaphoreInfo>; + friend class sp<SharedSemaphoreInfo>; + +public: + VkSemaphore semaphore() const { return mSemaphore; } - ~DestroySemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); } + GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; } }; static void destroy_semaphore(void* context) { - DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context); - delete info; + SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context); + info->decStrong(0); } VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) { ATRACE_NAME("Vulkan finish frame"); - VkExportSemaphoreCreateInfo exportInfo; - exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; - exportInfo.pNext = nullptr; - exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; - - VkSemaphoreCreateInfo semaphoreInfo; - semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; - semaphoreInfo.pNext = &exportInfo; - semaphoreInfo.flags = 0; - VkSemaphore semaphore; - VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); - ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore"); - - GrBackendSemaphore backendSemaphore; - backendSemaphore.initVulkan(semaphore); - + sp<SharedSemaphoreInfo> sharedSemaphore; GrFlushInfo flushInfo; - if (err == VK_SUCCESS) { - flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &backendSemaphore; - flushInfo.fFinishedProc = destroy_semaphore; - flushInfo.fFinishedContext = - new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore); - } else { - semaphore = VK_NULL_HANDLE; + + { + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = &exportInfo; + semaphoreInfo.flags = 0; + VkSemaphore semaphore; + VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + ALOGE_IF(VK_SUCCESS != err, + "VulkanManager::makeSwapSemaphore(): Failed to create semaphore"); + + if (err == VK_SUCCESS) { + sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore); + flushInfo.fNumSemaphores = 1; + flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore(); + flushInfo.fFinishedProc = destroy_semaphore; + sharedSemaphore->incStrong(0); + flushInfo.fFinishedContext = sharedSemaphore.get(); + } } + GrDirectContext* context = GrAsDirectContext(surface->recordingContext()); ALOGE_IF(!context, "Surface is not backed by gpu"); GrSemaphoresSubmitted submitted = context->flush( @@ -581,37 +594,34 @@ VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) { VkDrawResult drawResult{ .submissionTime = systemTime(), }; - if (semaphore != VK_NULL_HANDLE) { - if (submitted == GrSemaphoresSubmitted::kYes) { - if (mFrameBoundaryANDROID) { - // retrieve VkImage used as render target - VkImage image = VK_NULL_HANDLE; - GrBackendRenderTarget backendRenderTarget = - SkSurfaces::GetBackendRenderTarget( - surface, SkSurfaces::BackendHandleAccess::kFlushRead); - if (backendRenderTarget.isValid()) { - GrVkImageInfo info; - if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) { - image = info.fImage; - } else { - ALOGE("Frame boundary: backend is not vulkan"); - } + if (sharedSemaphore) { + if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) { + // retrieve VkImage used as render target + VkImage image = VK_NULL_HANDLE; + GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget( + surface, SkSurfaces::BackendHandleAccess::kFlushRead); + if (backendRenderTarget.isValid()) { + GrVkImageInfo info; + if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) { + image = info.fImage; } else { - ALOGE("Frame boundary: invalid backend render target"); + ALOGE("Frame boundary: backend is not vulkan"); } - // frameBoundaryANDROID needs to know about mSwapSemaphore, but - // it won't wait on it. - mFrameBoundaryANDROID(mDevice, semaphore, image); + } else { + ALOGE("Frame boundary: invalid backend render target"); } + // frameBoundaryANDROID needs to know about mSwapSemaphore, but + // it won't wait on it. + mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image); } VkSemaphoreGetFdInfoKHR getFdInfo; getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; getFdInfo.pNext = nullptr; - getFdInfo.semaphore = semaphore; + getFdInfo.semaphore = sharedSemaphore->semaphore(); getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; int fenceFd = -1; - err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); + VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd"); drawResult.presentFence.reset(fenceFd); } else { @@ -732,15 +742,15 @@ status_t VulkanManager::createReleaseFence(int* nativeFence, GrDirectContext* gr return INVALID_OPERATION; } - GrBackendSemaphore backendSemaphore; - backendSemaphore.initVulkan(semaphore); + auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore); // Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback GrFlushInfo flushInfo; flushInfo.fNumSemaphores = 1; - flushInfo.fSignalSemaphores = &backendSemaphore; + flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore(); flushInfo.fFinishedProc = destroy_semaphore; - flushInfo.fFinishedContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore); + sharedSemaphore->incStrong(0); + flushInfo.fFinishedContext = sharedSemaphore.get(); GrSemaphoresSubmitted submitted = grContext->flush(flushInfo); grContext->submit(); diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java index 985a7584ffe2..0f48abeb6882 100644 --- a/media/java/android/media/AudioHalVersionInfo.java +++ b/media/java/android/media/AudioHalVersionInfo.java @@ -22,6 +22,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; /** @@ -54,6 +56,7 @@ public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHa flag = false, prefix = "AUDIO_HAL_TYPE_", value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL}) + @Retention(RetentionPolicy.SOURCE) public @interface AudioHalType {} /** AudioHalVersionInfo object of all valid Audio HAL versions. */ diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index faa7f7fa3aa1..5331046dd0e3 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -2018,6 +2018,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #HDCP_V2_1}, * {@link #HDCP_V2_2}, * {@link #HDCP_V2_3} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) @@ -2121,6 +2123,8 @@ public final class MediaDrm implements AutoCloseable { * {@link #SECURITY_LEVEL_HW_SECURE_CRYPTO}, * {@link #SECURITY_LEVEL_HW_SECURE_DECODE}, * {@link #SECURITY_LEVEL_HW_SECURE_ALL} + * + * @removed mistakenly exposed previously */ @Deprecated @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index 74ca4b858ff6..99fcaf29688c 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -267,20 +267,26 @@ public class Spatializer { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingMode {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingMode {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_DISABLED, HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSet {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSet {}; /** @hide */ @IntDef(flag = false, value = { HEAD_TRACKING_MODE_RELATIVE_WORLD, HEAD_TRACKING_MODE_RELATIVE_DEVICE, - }) public @interface HeadTrackingModeSupported {}; + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HeadTrackingModeSupported {}; /** * @hide diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl new file mode 100644 index 000000000000..92cc923dc9ab --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdManager.aidl @@ -0,0 +1,25 @@ +/* + * 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.media.tv.ad; + +/** + * Interface to the TV AD service. + * @hide + */ +interface ITvAdManager { + void startAdService(in IBinder sessionToken, int userId); +} diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl new file mode 100644 index 000000000000..b834f1b9fb92 --- /dev/null +++ b/media/java/android/media/tv/ad/ITvAdSession.aidl @@ -0,0 +1,25 @@ +/* + * 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.media.tv.ad; + +/** + * Sub-interface of ITvAdService which is created per session and has its own context. + * @hide + */ +oneway interface ITvAdSession { + void startAdService(); +} diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java new file mode 100644 index 000000000000..aa5a290346b0 --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdManager.java @@ -0,0 +1,65 @@ +/* + * 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.media.tv.ad; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +/** + * Central system API to the overall client-side TV AD architecture, which arbitrates interaction + * between applications and AD services. + * @hide + */ +public class TvAdManager { + private static final String TAG = "TvAdManager"; + + private final ITvAdManager mService; + private final int mUserId; + + public TvAdManager(ITvAdManager service, int userId) { + mService = service; + mUserId = userId; + } + + /** + * The Session provides the per-session functionality of AD service. + */ + public static final class Session { + private final IBinder mToken; + private final ITvAdManager mService; + private final int mUserId; + + private Session(IBinder token, ITvAdManager service, int userId) { + mToken = token; + mService = service; + mUserId = userId; + } + + void startAdService() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startAdService(mToken, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java new file mode 100644 index 000000000000..61101f07923c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdService.java @@ -0,0 +1,60 @@ +/* + * 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.media.tv.ad; + +import android.app.Service; +import android.view.KeyEvent; + +/** + * The TvAdService class represents a TV client-side advertisement service. + * @hide + */ +public abstract class TvAdService extends Service { + private static final boolean DEBUG = false; + private static final String TAG = "TvAdService"; + + /** + * Base class for derived classes to implement to provide a TV AD session. + */ + public abstract static class Session implements KeyEvent.Callback { + /** + * Starts TvAdService session. + */ + public void onStartAdService() { + } + + void startAdService() { + onStartAdService(); + } + } + + /** + * Implements the internal ITvAdService interface. + */ + public static class ITvAdSessionWrapper extends ITvAdSession.Stub { + private final Session mSessionImpl; + + public ITvAdSessionWrapper(Session mSessionImpl) { + this.mSessionImpl = mSessionImpl; + } + + @Override + public void startAdService() { + mSessionImpl.startAdService(); + } + } +} diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java new file mode 100644 index 000000000000..1a3771a9f24c --- /dev/null +++ b/media/java/android/media/tv/ad/TvAdView.java @@ -0,0 +1,57 @@ +/* + * 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.media.tv.ad; + +import android.content.Context; +import android.util.Log; +import android.view.ViewGroup; + +/** + * Displays contents of TV AD services. + * @hide + */ +public class TvAdView extends ViewGroup { + private static final String TAG = "TvAdView"; + private static final boolean DEBUG = false; + + // TODO: create session + private TvAdManager.Session mSession; + + public TvAdView(Context context) { + super(context, /* attrs = */null, /* defStyleAttr = */0); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (DEBUG) { + Log.d(TAG, + "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)"); + } + } + + /** + * Starts the AD service. + */ + public void startAdService() { + if (DEBUG) { + Log.d(TAG, "start"); + } + if (mSession != null) { + mSession.startAdService(); + } + } +} diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index b0af09c19b7e..f4be33c7e6ea 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -335,6 +335,13 @@ LIBANDROID { APerformanceHint_closeSession; # introduced=Tiramisu APerformanceHint_setThreads; # introduced=UpsideDownCake APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream + APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream + AWorkDuration_create; # introduced=VanillaIceCream + AWorkDuration_release; # introduced=VanillaIceCream + AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream + AWorkDuration_setActualTotalDurationNanos; # introduced=VanillaIceCream + AWorkDuration_setActualCpuDurationNanos; # introduced=VanillaIceCream + AWorkDuration_setActualGpuDurationNanos; # introduced=VanillaIceCream local: *; }; diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index c25df6e08fd0..c4c81284780e 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -18,12 +18,14 @@ #include <aidl/android/hardware/power/SessionHint.h> #include <aidl/android/hardware/power/SessionMode.h> +#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> #include <binder/Binder.h> #include <binder/IBinder.h> #include <binder/IServiceManager.h> +#include <inttypes.h> #include <performance_hint_private.h> #include <utils/SystemClock.h> @@ -75,10 +77,13 @@ public: int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); + int reportActualWorkDuration(AWorkDuration* workDuration); private: friend struct APerformanceHintManager; + int reportActualWorkDurationInternal(WorkDuration* workDuration); + sp<IHintManager> mHintManager; sp<IHintSession> mHintSession; // HAL preferred update rate @@ -92,8 +97,7 @@ private: // Last hint reported from sendHint indexed by hint value std::vector<int64_t> mLastHintSentTimestamp; // Cached samples - std::vector<int64_t> mActualDurationsNanos; - std::vector<int64_t> mTimestampsNanos; + std::vector<WorkDuration> mActualWorkDurations; }; static IHintManager* gIHintManagerForTesting = nullptr; @@ -195,8 +199,7 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano * Most of the workload is target_duration dependent, so now clear the cached samples * as they are most likely obsolete. */ - mActualDurationsNanos.clear(); - mTimestampsNanos.clear(); + mActualWorkDurations.clear(); mFirstTargetMetTimestamp = 0; mLastTargetMetTimestamp = 0; return 0; @@ -207,43 +210,10 @@ int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNano ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); return EINVAL; } - int64_t now = elapsedRealtimeNano(); - mActualDurationsNanos.push_back(actualDurationNanos); - mTimestampsNanos.push_back(now); - if (actualDurationNanos >= mTargetDurationNanos) { - // Reset timestamps if we are equal or over the target. - mFirstTargetMetTimestamp = 0; - } else { - // Set mFirstTargetMetTimestamp for first time meeting target. - if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || - (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { - mFirstTargetMetTimestamp = now; - } - /** - * Rate limit the change if the update is over mPreferredRateNanos since first - * meeting target and less than mPreferredRateNanos since last meeting target. - */ - if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && - now - mLastTargetMetTimestamp <= mPreferredRateNanos) { - return 0; - } - mLastTargetMetTimestamp = now; - } + WorkDuration workDuration(0, actualDurationNanos, actualDurationNanos, 0); - binder::Status ret = - mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos); - if (!ret.isOk()) { - ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, - ret.exceptionMessage().c_str()); - mFirstTargetMetTimestamp = 0; - mLastTargetMetTimestamp = 0; - return EPIPE; - } - mActualDurationsNanos.clear(); - mTimestampsNanos.clear(); - - return 0; + return reportActualWorkDurationInternal(&workDuration); } int APerformanceHintSession::sendHint(SessionHint hint) { @@ -322,6 +292,67 @@ int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) { return OK; } +int APerformanceHintSession::reportActualWorkDuration(AWorkDuration* aWorkDuration) { + WorkDuration* workDuration = static_cast<WorkDuration*>(aWorkDuration); + if (workDuration->workPeriodStartTimestampNanos <= 0) { + ALOGE("%s: workPeriodStartTimestampNanos must be positive", __FUNCTION__); + return EINVAL; + } + if (workDuration->actualTotalDurationNanos <= 0) { + ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__); + return EINVAL; + } + if (workDuration->actualCpuDurationNanos <= 0) { + ALOGE("%s: cpuDurationNanos must be positive", __FUNCTION__); + return EINVAL; + } + if (workDuration->actualGpuDurationNanos < 0) { + ALOGE("%s: gpuDurationNanos must be non negative", __FUNCTION__); + return EINVAL; + } + + return reportActualWorkDurationInternal(workDuration); +} + +int APerformanceHintSession::reportActualWorkDurationInternal(WorkDuration* workDuration) { + int64_t actualTotalDurationNanos = workDuration->actualTotalDurationNanos; + int64_t now = uptimeNanos(); + workDuration->timestampNanos = now; + mActualWorkDurations.push_back(std::move(*workDuration)); + + if (actualTotalDurationNanos >= mTargetDurationNanos) { + // Reset timestamps if we are equal or over the target. + mFirstTargetMetTimestamp = 0; + } else { + // Set mFirstTargetMetTimestamp for first time meeting target. + if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp || + (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) { + mFirstTargetMetTimestamp = now; + } + /** + * Rate limit the change if the update is over mPreferredRateNanos since first + * meeting target and less than mPreferredRateNanos since last meeting target. + */ + if (now - mFirstTargetMetTimestamp > mPreferredRateNanos && + now - mLastTargetMetTimestamp <= mPreferredRateNanos) { + return 0; + } + mLastTargetMetTimestamp = now; + } + + binder::Status ret = mHintSession->reportActualWorkDuration2(mActualWorkDurations); + if (!ret.isOk()) { + ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__, + ret.exceptionMessage().c_str()); + mFirstTargetMetTimestamp = 0; + mLastTargetMetTimestamp = 0; + return ret.exceptionCode() == binder::Status::EX_ILLEGAL_ARGUMENT ? EINVAL : EPIPE; + } + mActualWorkDurations.clear(); + + return 0; +} + // ===================================== C API APerformanceHintManager* APerformanceHint_getManager() { return APerformanceHintManager::getInstance(); @@ -376,6 +407,64 @@ int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, return session->setPreferPowerEfficiency(enabled); } +int APerformanceHint_reportActualWorkDuration2(APerformanceHintSession* session, + AWorkDuration* workDuration) { + if (session == nullptr || workDuration == nullptr) { + ALOGE("Invalid value: (session %p, workDuration %p)", session, workDuration); + return EINVAL; + } + return session->reportActualWorkDuration(workDuration); +} + +AWorkDuration* AWorkDuration_create() { + WorkDuration* workDuration = new WorkDuration(); + return static_cast<AWorkDuration*>(workDuration); +} + +void AWorkDuration_release(AWorkDuration* aWorkDuration) { + if (aWorkDuration == nullptr) { + ALOGE("%s: aWorkDuration is nullptr", __FUNCTION__); + } + delete aWorkDuration; +} + +void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* aWorkDuration, + int64_t workPeriodStartTimestampNanos) { + if (aWorkDuration == nullptr || workPeriodStartTimestampNanos <= 0) { + ALOGE("%s: Invalid value. (AWorkDuration: %p, workPeriodStartTimestampNanos: %" PRIi64 ")", + __FUNCTION__, aWorkDuration, workPeriodStartTimestampNanos); + } + static_cast<WorkDuration*>(aWorkDuration)->workPeriodStartTimestampNanos = + workPeriodStartTimestampNanos; +} + +void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* aWorkDuration, + int64_t actualTotalDurationNanos) { + if (aWorkDuration == nullptr || actualTotalDurationNanos <= 0) { + ALOGE("%s: Invalid value. (AWorkDuration: %p, actualTotalDurationNanos: %" PRIi64 ")", + __FUNCTION__, aWorkDuration, actualTotalDurationNanos); + } + static_cast<WorkDuration*>(aWorkDuration)->actualTotalDurationNanos = actualTotalDurationNanos; +} + +void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* aWorkDuration, + int64_t actualCpuDurationNanos) { + if (aWorkDuration == nullptr || actualCpuDurationNanos <= 0) { + ALOGE("%s: Invalid value. (AWorkDuration: %p, actualCpuDurationNanos: %" PRIi64 ")", + __FUNCTION__, aWorkDuration, actualCpuDurationNanos); + } + static_cast<WorkDuration*>(aWorkDuration)->actualCpuDurationNanos = actualCpuDurationNanos; +} + +void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* aWorkDuration, + int64_t actualGpuDurationNanos) { + if (aWorkDuration == nullptr || actualGpuDurationNanos < 0) { + ALOGE("%s: Invalid value. (AWorkDuration: %p, actualGpuDurationNanos: %" PRIi64 ")", + __FUNCTION__, aWorkDuration, actualGpuDurationNanos); + } + static_cast<WorkDuration*>(aWorkDuration)->actualGpuDurationNanos = actualGpuDurationNanos; +} + void APerformanceHint_setIHintManagerForTesting(void* iManager) { delete gHintManagerForTesting; gHintManagerForTesting = nullptr; diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 22d33b139ccf..4553b4919d2d 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "PerformanceHintNativeTest" +#include <android/WorkDuration.h> #include <android/os/IHintManager.h> #include <android/os/IHintSession.h> #include <android/performance_hint.h> @@ -60,6 +61,8 @@ public: MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override)); MOCK_METHOD(Status, close, (), (override)); MOCK_METHOD(IBinder*, onAsBinder, (), (override)); + MOCK_METHOD(Status, reportActualWorkDuration2, + (const ::std::vector<android::os::WorkDuration>& workDurations), (override)); }; class PerformanceHintTest : public Test { @@ -120,6 +123,7 @@ TEST_F(PerformanceHintTest, TestSession) { std::vector<int64_t> actualDurations; actualDurations.push_back(20); EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1)); + EXPECT_CALL(*iSession, reportActualWorkDuration2(_)).Times(Exactly(1)); result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos); EXPECT_EQ(0, result); @@ -238,4 +242,125 @@ TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) { APerformanceHintSession* session = APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); ASSERT_TRUE(session); -}
\ No newline at end of file +} + +MATCHER_P(WorkDurationEq, expected, "") { + if (arg.size() != expected.size()) { + *result_listener << "WorkDuration vectors are different sizes. Expected: " + << expected.size() << ", Actual: " << arg.size(); + return false; + } + for (int i = 0; i < expected.size(); ++i) { + android::os::WorkDuration expectedWorkDuration = expected[i]; + android::os::WorkDuration actualWorkDuration = arg[i]; + if (!expectedWorkDuration.equalsWithoutTimestamp(actualWorkDuration)) { + *result_listener << "WorkDuration at [" << i << "] is different: " + << "Expected: " << expectedWorkDuration + << ", Actual: " << actualWorkDuration; + return false; + } + } + return true; +} + +TEST_F(PerformanceHintTest, TestAPerformanceHint_reportActualWorkDuration2) { + APerformanceHintManager* manager = createManager(); + + std::vector<int32_t> tids; + tids.push_back(1); + tids.push_back(2); + int64_t targetDuration = 56789L; + + StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>(); + sp<IHintSession> session_sp(iSession); + + EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _)) + .Times(Exactly(1)) + .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status()))); + + APerformanceHintSession* session = + APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration); + ASSERT_TRUE(session); + + int64_t targetDurationNanos = 10; + EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1)); + int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); + EXPECT_EQ(0, result); + + usleep(2); // Sleep for longer than preferredUpdateRateNanos. + { + std::vector<android::os::WorkDuration> actualWorkDurations; + android::os::WorkDuration workDuration(1, 20, 13, 8); + actualWorkDurations.push_back(workDuration); + + EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) + .Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration2(session, + static_cast<AWorkDuration*>( + &workDuration)); + EXPECT_EQ(0, result); + } + + { + std::vector<android::os::WorkDuration> actualWorkDurations; + android::os::WorkDuration workDuration(-1, 20, 13, 8); + actualWorkDurations.push_back(workDuration); + + EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) + .Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration2(session, + static_cast<AWorkDuration*>( + &workDuration)); + EXPECT_EQ(22, result); + } + { + std::vector<android::os::WorkDuration> actualWorkDurations; + android::os::WorkDuration workDuration(1, -20, 13, 8); + actualWorkDurations.push_back(workDuration); + + EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) + .Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration2(session, + static_cast<AWorkDuration*>( + &workDuration)); + EXPECT_EQ(22, result); + } + { + std::vector<android::os::WorkDuration> actualWorkDurations; + android::os::WorkDuration workDuration(1, 20, -13, 8); + actualWorkDurations.push_back(workDuration); + + EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) + .Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration2(session, + static_cast<AWorkDuration*>( + &workDuration)); + EXPECT_EQ(EINVAL, result); + } + { + std::vector<android::os::WorkDuration> actualWorkDurations; + android::os::WorkDuration workDuration(1, 20, 13, -8); + actualWorkDurations.push_back(workDuration); + + EXPECT_CALL(*iSession, reportActualWorkDuration2(WorkDurationEq(actualWorkDurations))) + .Times(Exactly(1)); + result = APerformanceHint_reportActualWorkDuration2(session, + static_cast<AWorkDuration*>( + &workDuration)); + EXPECT_EQ(EINVAL, result); + } + + EXPECT_CALL(*iSession, close()).Times(Exactly(1)); + APerformanceHint_closeSession(session); +} + +TEST_F(PerformanceHintTest, TestAWorkDuration) { + AWorkDuration* aWorkDuration = AWorkDuration_create(); + ASSERT_NE(aWorkDuration, nullptr); + + AWorkDuration_setWorkPeriodStartTimestampNanos(aWorkDuration, 1); + AWorkDuration_setActualTotalDurationNanos(aWorkDuration, 20); + AWorkDuration_setActualCpuDurationNanos(aWorkDuration, 13); + AWorkDuration_setActualGpuDurationNanos(aWorkDuration, 8); + AWorkDuration_release(aWorkDuration); +} diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 58224b817a4d..38bd7d5f3944 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -46,6 +46,9 @@ android_app { "xz-java", "androidx.leanback_leanback", "androidx.annotation_annotation", + "androidx.fragment_fragment", + "androidx.lifecycle_lifecycle-livedata", + "androidx.lifecycle_lifecycle-extensions", ], lint: { @@ -69,6 +72,9 @@ android_app { static_libs: [ "xz-java", "androidx.leanback_leanback", + "androidx.fragment_fragment", + "androidx.lifecycle_lifecycle-livedata", + "androidx.lifecycle_lifecycle-extensions", ], aaptflags: ["--product tablet"], @@ -94,6 +100,9 @@ android_app { "xz-java", "androidx.leanback_leanback", "androidx.annotation_annotation", + "androidx.fragment_fragment", + "androidx.lifecycle_lifecycle-livedata", + "androidx.lifecycle_lifecycle-extensions", ], aaptflags: ["--product tv"], diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index a16f9f55b466..35f57723be7a 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -43,6 +43,11 @@ </intent-filter> </receiver> + <activity android:name=".v2.ui.InstallLaunch" + android:configChanges="orientation|keyboardHidden|screenSize" + android:theme="@style/Theme.AlertDialogActivity" + android:exported="true"/> + <activity android:name=".InstallStart" android:theme="@style/Theme.AlertDialogActivity" android:exported="true" diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 736e0efe872a..e2107ebe2525 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -40,7 +40,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import com.android.packageinstaller.v2.ui.InstallLaunch; import java.util.Arrays; /** @@ -57,9 +57,23 @@ public class InstallStart extends Activity { private final boolean mLocalLOGV = false; + // TODO (sumedhsen): Replace with an Android Feature Flag once implemented + private static final boolean USE_PIA_V2 = false; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (USE_PIA_V2) { + Intent piaV2 = new Intent(getIntent()); + piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_NAME, getCallingPackage()); + piaV2.putExtra(InstallLaunch.EXTRA_CALLING_PKG_UID, getLaunchedFromUid()); + piaV2.setClass(this, InstallLaunch.class); + piaV2.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(piaV2); + finish(); + return; + } mPackageManager = getPackageManager(); mUserManager = getSystemService(UserManager.class); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java new file mode 100644 index 000000000000..03af95180009 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model; + +import static com.android.packageinstaller.v2.model.PackageUtil.canPackageQuery; +import static com.android.packageinstaller.v2.model.PackageUtil.isCallerSessionOwner; +import static com.android.packageinstaller.v2.model.PackageUtil.isInstallPermissionGrantedOrRequested; +import static com.android.packageinstaller.v2.model.PackageUtil.isPermissionGranted; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY; + +import android.Manifest; +import android.app.Activity; +import android.app.admin.DevicePolicyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.v2.model.installstagedata.InstallAborted; +import com.android.packageinstaller.v2.model.installstagedata.InstallReady; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.model.installstagedata.InstallStaging; +import java.io.IOException; + +public class InstallRepository { + + private static final String SCHEME_PACKAGE = "package"; + private static final String TAG = InstallRepository.class.getSimpleName(); + private final Context mContext; + private final PackageManager mPackageManager; + private final PackageInstaller mPackageInstaller; + private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; + private final MutableLiveData<InstallStage> mStagingResult = new MutableLiveData<>(); + private final boolean mLocalLOGV = false; + private Intent mIntent; + private boolean mIsSessionInstall; + private boolean mIsTrustedSource; + /** + * Session ID for a session created when caller uses PackageInstaller APIs + */ + private int mSessionId; + /** + * Session ID for a session created by this app + */ + private int mStagedSessionId = SessionInfo.INVALID_ID; + private int mCallingUid; + private String mCallingPackage; + private SessionStager mSessionStager; + + public InstallRepository(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + mPackageInstaller = mPackageManager.getPackageInstaller(); + mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); + mUserManager = context.getSystemService(UserManager.class); + } + + /** + * Extracts information from the incoming install intent, checks caller's permission to install + * packages, verifies that the caller is the install session owner (in case of a session based + * install) and checks if the current user has restrictions set that prevent app installation, + * + * @param intent the incoming {@link Intent} object for installing a package + * @param callerInfo {@link CallerInfo} that holds the callingUid and callingPackageName + * @return <p>{@link InstallAborted} if there are errors while performing the checks</p> + * <p>{@link InstallStaging} after successfully performing the checks</p> + */ + public InstallStage performPreInstallChecks(Intent intent, CallerInfo callerInfo) { + mIntent = intent; + + String callingAttributionTag = null; + + mIsSessionInstall = + PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()) + || PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction()); + + mSessionId = mIsSessionInstall + ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, SessionInfo.INVALID_ID) + : SessionInfo.INVALID_ID; + + mCallingPackage = callerInfo.getPackageName(); + + if (mCallingPackage == null && mSessionId != SessionInfo.INVALID_ID) { + PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(mSessionId); + mCallingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null; + callingAttributionTag = + (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null; + } + + // Uid of the source package, coming from ActivityManager + mCallingUid = callerInfo.getUid(); + if (mCallingUid == Process.INVALID_UID) { + Log.e(TAG, "Could not determine the launching uid."); + } + final ApplicationInfo sourceInfo = getSourceInfo(mCallingPackage); + // Uid of the source package, with a preference to uid from ApplicationInfo + final int originatingUid = sourceInfo != null ? sourceInfo.uid : mCallingUid; + + if (mCallingUid == Process.INVALID_UID && sourceInfo == null) { + // Caller's identity could not be determined. Abort the install + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + + if (!isCallerSessionOwner(mPackageInstaller, originatingUid, mSessionId)) { + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + + mIsTrustedSource = isInstallRequestFromTrustedSource(sourceInfo, mIntent, originatingUid); + + if (!isInstallPermissionGrantedOrRequested(mContext, mCallingUid, originatingUid, + mIsTrustedSource)) { + return new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR).build(); + } + + String restriction = getDevicePolicyRestrictions(); + if (restriction != null) { + InstallAborted.Builder abortedBuilder = + new InstallAborted.Builder(ABORT_REASON_POLICY).setMessage(restriction); + final Intent adminSupportDetailsIntent = + mDevicePolicyManager.createAdminSupportIntent(restriction); + if (adminSupportDetailsIntent != null) { + abortedBuilder.setResultIntent(adminSupportDetailsIntent); + } + return abortedBuilder.build(); + } + + maybeRemoveInvalidInstallerPackageName(callerInfo); + + return new InstallStaging(); + } + + /** + * @return the ApplicationInfo for the installation source (the calling package), if available + */ + @Nullable + private ApplicationInfo getSourceInfo(@Nullable String callingPackage) { + if (callingPackage == null) { + return null; + } + try { + return mPackageManager.getApplicationInfo(callingPackage, 0); + } catch (PackageManager.NameNotFoundException ignored) { + return null; + } + } + + private boolean isInstallRequestFromTrustedSource(ApplicationInfo sourceInfo, Intent intent, + int originatingUid) { + boolean isNotUnknownSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false); + return sourceInfo != null && sourceInfo.isPrivilegedApp() + && (isNotUnknownSource + || isPermissionGranted(mContext, Manifest.permission.INSTALL_PACKAGES, originatingUid)); + } + + private String getDevicePolicyRestrictions() { + final String[] restrictions = new String[]{ + UserManager.DISALLOW_INSTALL_APPS, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY + }; + + for (String restriction : restrictions) { + if (!mUserManager.hasUserRestrictionForUser(restriction, Process.myUserHandle())) { + continue; + } + return restriction; + } + return null; + } + + private void maybeRemoveInvalidInstallerPackageName(CallerInfo callerInfo) { + final String installerPackageNameFromIntent = + mIntent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); + if (installerPackageNameFromIntent == null) { + return; + } + if (!TextUtils.equals(installerPackageNameFromIntent, callerInfo.getPackageName()) + && !isPermissionGranted(mPackageManager, Manifest.permission.INSTALL_PACKAGES, + callerInfo.getPackageName())) { + Log.e(TAG, "The given installer package name " + installerPackageNameFromIntent + + " is invalid. Remove it."); + EventLog.writeEvent(0x534e4554, "236687884", callerInfo.getUid(), + "Invalid EXTRA_INSTALLER_PACKAGE_NAME"); + mIntent.removeExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME); + } + } + + public void stageForInstall() { + Uri uri = mIntent.getData(); + if (mIsSessionInstall || (uri != null && SCHEME_PACKAGE.equals(uri.getScheme()))) { + // For a session based install or installing with a package:// URI, there is no file + // for us to stage. Setting the mStagingResult as null will signal InstallViewModel to + // proceed with user confirmation stage. + mStagingResult.setValue(new InstallReady()); + return; + } + if (uri != null + && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) + && canPackageQuery(mContext, mCallingUid, uri)) { + + if (mStagedSessionId > 0) { + final PackageInstaller.SessionInfo info = + mPackageInstaller.getSessionInfo(mStagedSessionId); + if (info == null || !info.isActive() || info.getResolvedBaseApkPath() == null) { + Log.w(TAG, "Session " + mStagedSessionId + " in funky state; ignoring"); + if (info != null) { + cleanupStagingSession(); + } + mStagedSessionId = 0; + } + } + + // Session does not exist, or became invalid. + if (mStagedSessionId <= 0) { + // Create session here to be able to show error. + try (final AssetFileDescriptor afd = + mContext.getContentResolver().openAssetFileDescriptor(uri, "r")) { + ParcelFileDescriptor pfd = afd != null ? afd.getParcelFileDescriptor() : null; + PackageInstaller.SessionParams params = + createSessionParams(mIntent, pfd, uri.toString()); + mStagedSessionId = mPackageInstaller.createSession(params); + } catch (IOException e) { + Log.w(TAG, "Failed to create a staging session", e); + mStagingResult.setValue( + new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, + PackageManager.INSTALL_FAILED_INVALID_APK)) + .setActivityResultCode(Activity.RESULT_FIRST_USER) + .build()); + return; + } + } + + SessionStageListener listener = new SessionStageListener() { + @Override + public void onStagingSuccess(SessionInfo info) { + //TODO: Verify if the returned sessionInfo should be used anywhere + mStagingResult.setValue(new InstallReady()); + } + + @Override + public void onStagingFailure() { + cleanupStagingSession(); + mStagingResult.setValue( + new InstallAborted.Builder(ABORT_REASON_INTERNAL_ERROR) + .setResultIntent(new Intent().putExtra(Intent.EXTRA_INSTALL_RESULT, + PackageManager.INSTALL_FAILED_INVALID_APK)) + .setActivityResultCode(Activity.RESULT_FIRST_USER) + .build()); + } + }; + if (mSessionStager != null) { + mSessionStager.cancel(true); + } + mSessionStager = new SessionStager(mContext, uri, mStagedSessionId, listener); + mSessionStager.execute(); + } + } + + private void cleanupStagingSession() { + if (mStagedSessionId > 0) { + try { + mPackageInstaller.abandonSession(mStagedSessionId); + } catch (SecurityException ignored) { + } + mStagedSessionId = 0; + } + } + + private PackageInstaller.SessionParams createSessionParams(@NonNull Intent intent, + @Nullable ParcelFileDescriptor pfd, @NonNull String debugPathName) { + PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + final Uri referrerUri = intent.getParcelableExtra(Intent.EXTRA_REFERRER, Uri.class); + params.setPackageSource( + referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE + : PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE); + params.setInstallAsInstantApp(false); + params.setReferrerUri(referrerUri); + params.setOriginatingUri( + intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI, Uri.class)); + params.setOriginatingUid(intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID, + Process.INVALID_UID)); + params.setInstallerPackageName(intent.getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME)); + params.setInstallReason(PackageManager.INSTALL_REASON_USER); + // Disable full screen intent usage by for sideloads. + params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT, + PackageInstaller.SessionParams.PERMISSION_STATE_DENIED); + + if (pfd != null) { + try { + final PackageInstaller.InstallInfo result = mPackageInstaller.readInstallInfo(pfd, + debugPathName, 0); + params.setAppPackageName(result.getPackageName()); + params.setInstallLocation(result.getInstallLocation()); + params.setSize(result.calculateInstalledSize(params, pfd)); + } catch (PackageInstaller.PackageParsingException e) { + Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults.", e); + params.setSize(pfd.getStatSize()); + } catch (IOException e) { + Log.e(TAG, + "Cannot calculate installed size " + debugPathName + + ". Try only apk size.", e); + } + } else { + Log.e(TAG, "Cannot parse package " + debugPathName + ". Assuming defaults."); + } + return params; + } + + public MutableLiveData<Integer> getStagingProgress() { + if (mSessionStager != null) { + return mSessionStager.getProgress(); + } + return new MutableLiveData<>(0); + } + + public MutableLiveData<InstallStage> getStagingResult() { + return mStagingResult; + } + + public interface SessionStageListener { + + void onStagingSuccess(SessionInfo info); + + void onStagingFailure(); + } + + public static class CallerInfo { + + private final String mPackageName; + private final int mUid; + + public CallerInfo(String packageName, int uid) { + mPackageName = packageName; + mUid = uid; + } + + public String getPackageName() { + return mPackageName; + } + + public int getUid() { + return mUid; + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java new file mode 100644 index 000000000000..82a8c9598051 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model; + +import android.Manifest; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Process; +import android.util.Log; +import androidx.annotation.NonNull; +import java.util.Arrays; + +public class PackageUtil { + + private static final String TAG = InstallRepository.class.getSimpleName(); + private static final String DOWNLOADS_AUTHORITY = "downloads"; + + /** + * Determines if the UID belongs to the system downloads provider and returns the + * {@link ApplicationInfo} of the provider + * + * @param uid UID of the caller + * @return {@link ApplicationInfo} of the provider if a downloads provider exists, it is a + * system app, and its UID matches with the passed UID, null otherwise. + */ + public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) { + final ProviderInfo providerInfo = pm.resolveContentProvider( + DOWNLOADS_AUTHORITY, 0); + if (providerInfo == null) { + // There seems to be no currently enabled downloads provider on the system. + return null; + } + ApplicationInfo appInfo = providerInfo.applicationInfo; + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) { + return appInfo; + } + return null; + } + + /** + * Get the maximum target sdk for a UID. + * + * @param context The context to use + * @param uid The UID requesting the install/uninstall + * @return The maximum target SDK or -1 if the uid does not match any packages. + */ + public static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) { + PackageManager pm = context.getPackageManager(); + final String[] packages = pm.getPackagesForUid(uid); + int targetSdkVersion = -1; + if (packages != null) { + for (String packageName : packages) { + try { + ApplicationInfo info = pm.getApplicationInfo(packageName, 0); + targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion); + } catch (PackageManager.NameNotFoundException e) { + // Ignore and try the next package + } + } + } + return targetSdkVersion; + } + + public static boolean canPackageQuery(Context context, int callingUid, Uri packageUri) { + PackageManager pm = context.getPackageManager(); + ProviderInfo info = pm.resolveContentProvider(packageUri.getAuthority(), + PackageManager.ComponentInfoFlags.of(0)); + if (info == null) { + return false; + } + String targetPackage = info.packageName; + + String[] callingPackages = pm.getPackagesForUid(callingUid); + if (callingPackages == null) { + return false; + } + for (String callingPackage : callingPackages) { + try { + if (pm.canPackageQuery(callingPackage, targetPackage)) { + return true; + } + } catch (PackageManager.NameNotFoundException e) { + // no-op + } + } + return false; + } + + /** + * @param context the {@link Context} object + * @param permission the permission name to check + * @param callingUid the UID of the caller who's permission is being checked + * @return {@code true} if the callingUid is granted the said permission + */ + public static boolean isPermissionGranted(Context context, String permission, int callingUid) { + return context.checkPermission(permission, -1, callingUid) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * @param pm the {@link PackageManager} object + * @param permission the permission name to check + * @param packageName the name of the package who's permission is being checked + * @return {@code true} if the package is granted the said permission + */ + public static boolean isPermissionGranted(PackageManager pm, String permission, + String packageName) { + return pm.checkPermission(permission, packageName) == PackageManager.PERMISSION_GRANTED; + } + + /** + * @param context the {@link Context} object + * @param callingUid the UID of the caller who's permission is being checked + * @param originatingUid the UID from where install is being originated. This could be same as + * callingUid or it will be the UID of the package performing a session based install + * @param isTrustedSource whether install request is coming from a privileged app or an app that + * has {@link Manifest.permission.INSTALL_PACKAGES} permission granted + * @return {@code true} if the package is granted the said permission + */ + public static boolean isInstallPermissionGrantedOrRequested(Context context, int callingUid, + int originatingUid, boolean isTrustedSource) { + boolean isDocumentsManager = + isPermissionGranted(context, Manifest.permission.MANAGE_DOCUMENTS, callingUid); + boolean isSystemDownloadsProvider = + getSystemDownloadsProviderInfo(context.getPackageManager(), callingUid) != null; + + if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager) { + + final int targetSdkVersion = getMaxTargetSdkVersionForUid(context, originatingUid); + if (targetSdkVersion < 0) { + // Invalid originating uid supplied. Abort install. + Log.w(TAG, "Cannot get target sdk version for uid " + originatingUid); + return false; + } else if (targetSdkVersion >= Build.VERSION_CODES.O + && !isUidRequestingPermission(context.getPackageManager(), originatingUid, + Manifest.permission.REQUEST_INSTALL_PACKAGES)) { + Log.e(TAG, "Requesting uid " + originatingUid + " needs to declare permission " + + Manifest.permission.REQUEST_INSTALL_PACKAGES); + return false; + } + } + return true; + } + + /** + * @param pm the {@link PackageManager} object + * @param uid the UID of the caller who's permission is being checked + * @param permission the permission name to check + * @return {@code true} if the caller is requesting the said permission in its Manifest + */ + public static boolean isUidRequestingPermission(PackageManager pm, int uid, String permission) { + final String[] packageNames = pm.getPackagesForUid(uid); + if (packageNames == null) { + return false; + } + for (final String packageName : packageNames) { + final PackageInfo packageInfo; + try { + packageInfo = pm.getPackageInfo(packageName, + PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + // Ignore and try the next package + continue; + } + if (packageInfo.requestedPermissions != null + && Arrays.asList(packageInfo.requestedPermissions).contains(permission)) { + return true; + } + } + return false; + } + + /** + * @param pi the {@link PackageInstaller} object to use + * @param originatingUid the UID of the package performing a session based install + * @param sessionId ID of the install session + * @return {@code true} if the caller is the session owner + */ + public static boolean isCallerSessionOwner(PackageInstaller pi, int originatingUid, + int sessionId) { + if (sessionId == SessionInfo.INVALID_ID) { + return false; + } + if (originatingUid == Process.ROOT_UID) { + return true; + } + PackageInstaller.SessionInfo sessionInfo = pi.getSessionInfo(sessionId); + if (sessionInfo == null) { + return false; + } + int installerUid = sessionInfo.getInstallerUid(); + return originatingUid == installerUid; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java new file mode 100644 index 000000000000..a2c81f11cf68 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/SessionStager.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model; + +import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH; + +import android.content.Context; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; +import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.v2.model.InstallRepository.SessionStageListener; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class SessionStager extends AsyncTask<Void, Integer, SessionInfo> { + + private static final String TAG = SessionStager.class.getSimpleName(); + private final Context mContext; + private final Uri mUri; + private final int mStagedSessionId; + private final MutableLiveData<Integer> mProgressLiveData = new MutableLiveData<>(0); + private final SessionStageListener mListener; + + SessionStager(Context context, Uri uri, int stagedSessionId, SessionStageListener listener) { + mContext = context; + mUri = uri; + mStagedSessionId = stagedSessionId; + mListener = listener; + } + + @Override + protected PackageInstaller.SessionInfo doInBackground(Void... params) { + PackageInstaller pi = mContext.getPackageManager().getPackageInstaller(); + try (PackageInstaller.Session session = pi.openSession(mStagedSessionId); + InputStream in = mContext.getContentResolver().openInputStream(mUri)) { + session.setStagingProgress(0); + + if (in == null) { + return null; + } + final long sizeBytes = getContentSizeBytes(); + mProgressLiveData.postValue(sizeBytes > 0 ? 0 : -1); + + long totalRead = 0; + try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) { + byte[] buffer = new byte[1024 * 1024]; + while (true) { + int numRead = in.read(buffer); + + if (numRead == -1) { + session.fsync(out); + break; + } + + if (isCancelled()) { + break; + } + + out.write(buffer, 0, numRead); + if (sizeBytes > 0) { + totalRead += numRead; + float fraction = ((float) totalRead / (float) sizeBytes); + session.setStagingProgress(fraction); + publishProgress((int) (fraction * 100.0)); + } + } + } + return pi.getSessionInfo(mStagedSessionId); + } catch (IOException | SecurityException | IllegalStateException + | IllegalArgumentException e) { + Log.w(TAG, "Error staging apk from content URI", e); + return null; + } + } + + private long getContentSizeBytes() { + try (AssetFileDescriptor afd = mContext.getContentResolver() + .openAssetFileDescriptor(mUri, "r")) { + return afd != null ? afd.getLength() : UNKNOWN_LENGTH; + } catch (IOException e) { + Log.w(TAG, "Failed to open asset file descriptor", e); + return UNKNOWN_LENGTH; + } + } + + public MutableLiveData<Integer> getProgress() { + return mProgressLiveData; + } + + @Override + protected void onProgressUpdate(Integer... progress) { + if (progress != null && progress.length > 0) { + mProgressLiveData.setValue(progress[0]); + } + } + + @Override + protected void onPostExecute(SessionInfo sessionInfo) { + if (sessionInfo == null || !sessionInfo.isActive() + || sessionInfo.getResolvedBaseApkPath() == null) { + Log.w(TAG, "Session info is invalid: " + sessionInfo); + mListener.onStagingFailure(); + return; + } + mListener.onStagingSuccess(sessionInfo); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java new file mode 100644 index 000000000000..cc9857d45bc4 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallAborted.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.installstagedata; + + +import android.app.Activity; +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class InstallAborted extends InstallStage { + + public static final int ABORT_REASON_INTERNAL_ERROR = 0; + public static final int ABORT_REASON_POLICY = 1; + private final int mStage = InstallStage.STAGE_ABORTED; + private final int mAbortReason; + + /** + * It will hold the restriction name, when the restriction was enforced by the system, and not + * a device admin. + */ + @NonNull + private final String mMessage; + /** + * <p>If abort reason is ABORT_REASON_POLICY, then this will hold the Intent + * to display a support dialog when a feature was disabled by an admin. It will be + * {@code null} if the feature is disabled by the system. In this case, the restriction name + * will be set in {@link #mMessage} </p> + * + * <p>If the abort reason is ABORT_REASON_INTERNAL_ERROR, it <b>may</b> hold an + * intent to be sent as a result to the calling activity.</p> + */ + @Nullable + private final Intent mIntent; + private final int mActivityResultCode; + + private InstallAborted(int reason, @NonNull String message, @Nullable Intent intent, + int activityResultCode) { + mAbortReason = reason; + mMessage = message; + mIntent = intent; + mActivityResultCode = activityResultCode; + } + + public int getAbortReason() { + return mAbortReason; + } + + @NonNull + public String getMessage() { + return mMessage; + } + + @Nullable + public Intent getResultIntent() { + return mIntent; + } + + public int getActivityResultCode() { + return mActivityResultCode; + } + + @Override + public int getStageCode() { + return mStage; + } + + public static class Builder { + + private final int mAbortReason; + private String mMessage = ""; + private Intent mIntent = null; + private int mActivityResultCode = Activity.RESULT_CANCELED; + + public Builder(int reason) { + mAbortReason = reason; + } + + public Builder setMessage(@NonNull String message) { + mMessage = message; + return this; + } + + public Builder setResultIntent(@NonNull Intent intent) { + mIntent = intent; + return this; + } + + public Builder setActivityResultCode(int resultCode) { + mActivityResultCode = resultCode; + return this; + } + + public InstallAborted build() { + return new InstallAborted(mAbortReason, mMessage, mIntent, mActivityResultCode); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java new file mode 100644 index 000000000000..548f2c544da7 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallReady.java @@ -0,0 +1,27 @@ +/* + * 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 + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.installstagedata; + +public class InstallReady extends InstallStage{ + + private final int mStage = InstallStage.STAGE_READY; + + @Override + public int getStageCode() { + return mStage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java new file mode 100644 index 000000000000..f91e64bdc326 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStage.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.installstagedata; + +public abstract class InstallStage { + + public static final int STAGE_DEFAULT = -1; + public static final int STAGE_ABORTED = 0; + public static final int STAGE_STAGING = 1; + public static final int STAGE_READY = 2; + public static final int STAGE_USER_ACTION_REQUIRED = 3; + public static final int STAGE_INSTALLING = 4; + public static final int STAGE_SUCCESS = 5; + public static final int STAGE_FAILED = 6; + + /** + * @return the integer value representing current install stage. + */ + public abstract int getStageCode(); +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java new file mode 100644 index 000000000000..a979cf87c350 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/installstagedata/InstallStaging.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.model.installstagedata; + +public class InstallStaging extends InstallStage { + + private final int mStage = InstallStage.STAGE_STAGING; + + @Override + public int getStageCode() { + return mStage; + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java new file mode 100644 index 000000000000..ba5a0cdc7b0d --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui; + +import static android.os.Process.INVALID_UID; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_INTERNAL_ERROR; +import static com.android.packageinstaller.v2.model.installstagedata.InstallAborted.ABORT_REASON_POLICY; + +import android.content.Intent; +import android.os.Bundle; +import android.os.UserManager; +import android.util.Log; +import android.view.Window; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.ViewModelProvider; +import com.android.packageinstaller.R; +import com.android.packageinstaller.v2.model.InstallRepository; +import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo; +import com.android.packageinstaller.v2.model.installstagedata.InstallAborted; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment; +import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment; +import com.android.packageinstaller.v2.viewmodel.InstallViewModel; +import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory; + +public class InstallLaunch extends FragmentActivity { + + public static final String EXTRA_CALLING_PKG_UID = + InstallLaunch.class.getPackageName() + ".callingPkgUid"; + public static final String EXTRA_CALLING_PKG_NAME = + InstallLaunch.class.getPackageName() + ".callingPkgName"; + private static final String TAG = InstallLaunch.class.getSimpleName(); + private static final String TAG_DIALOG = "dialog"; + private final boolean mLocalLOGV = false; + private InstallViewModel mInstallViewModel; + private InstallRepository mInstallRepository; + + private FragmentManager mFragmentManager; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.requestWindowFeature(Window.FEATURE_NO_TITLE); + + mFragmentManager = getSupportFragmentManager(); + mInstallRepository = new InstallRepository(getApplicationContext()); + mInstallViewModel = new ViewModelProvider(this, + new InstallViewModelFactory(this.getApplication(), mInstallRepository)).get( + InstallViewModel.class); + + Intent intent = getIntent(); + CallerInfo info = new CallerInfo( + intent.getStringExtra(EXTRA_CALLING_PKG_NAME), + intent.getIntExtra(EXTRA_CALLING_PKG_UID, INVALID_UID)); + mInstallViewModel.preprocessIntent(intent, info); + + mInstallViewModel.getCurrentInstallStage().observe(this, this::onInstallStageChange); + } + + /** + * Main controller of the UI. This method shows relevant dialogs based on the install stage + */ + private void onInstallStageChange(InstallStage installStage) { + if (installStage.getStageCode() == InstallStage.STAGE_STAGING) { + InstallStagingFragment stagingDialog = new InstallStagingFragment(); + showDialogInner(stagingDialog); + mInstallViewModel.getStagingProgress().observe(this, stagingDialog::setProgress); + } else if (installStage.getStageCode() == InstallStage.STAGE_ABORTED) { + InstallAborted aborted = (InstallAborted) installStage; + switch (aborted.getAbortReason()) { + // TODO: check if any dialog is to be shown for ABORT_REASON_INTERNAL_ERROR + case ABORT_REASON_INTERNAL_ERROR -> setResult(RESULT_CANCELED, true); + case ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted); + default -> setResult(RESULT_CANCELED, true); + } + } else { + Log.d(TAG, "Unimplemented stage: " + installStage.getStageCode()); + showDialogInner(null); + } + } + + private void showPolicyRestrictionDialog(InstallAborted aborted) { + String restriction = aborted.getMessage(); + Intent adminSupportIntent = aborted.getResultIntent(); + boolean shouldFinish; + + // If the given restriction is set by an admin, display information about the + // admin enforcing the restriction for the affected user. If not enforced by the admin, + // show the system dialog. + if (adminSupportIntent != null) { + if (mLocalLOGV) { + Log.i(TAG, "Restriction set by admin, starting " + adminSupportIntent); + } + startActivity(adminSupportIntent); + // Finish the package installer app since the next dialog will not be shown by this app + shouldFinish = true; + } else { + if (mLocalLOGV) { + Log.i(TAG, "Restriction set by system: " + restriction); + } + DialogFragment blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction); + // Don't finish the package installer app since the next dialog + // will be shown by this app + shouldFinish = false; + showDialogInner(blockedByPolicyDialog); + } + setResult(RESULT_CANCELED, shouldFinish); + } + + /** + * Create a new dialog based on the install restriction enforced. + * + * @param restriction The restriction to create the dialog for + * @return The dialog + */ + private DialogFragment createDevicePolicyRestrictionDialog(String restriction) { + if (mLocalLOGV) { + Log.i(TAG, "createDialog(" + restriction + ")"); + } + return switch (restriction) { + case UserManager.DISALLOW_INSTALL_APPS -> + new SimpleErrorFragment(R.string.install_apps_user_restriction_dlg_text); + case UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY -> + new SimpleErrorFragment(R.string.unknown_apps_user_restriction_dlg_text); + default -> null; + }; + } + + /** + * Replace any visible dialog by the dialog returned by InstallRepository + * + * @param newDialog The new dialog to display + */ + private void showDialogInner(@Nullable DialogFragment newDialog) { + DialogFragment currentDialog = (DialogFragment) mFragmentManager.findFragmentByTag( + TAG_DIALOG); + if (currentDialog != null) { + currentDialog.dismissAllowingStateLoss(); + } + if (newDialog != null) { + newDialog.show(mFragmentManager, TAG_DIALOG); + } + } + + public void setResult(int resultCode, boolean shouldFinish) { + // TODO: This is incomplete. We need to send RESULT_FIRST_USER, RESULT_OK etc + // for relevant use cases. Investigate when to send what result. + super.setResult(resultCode); + if (shouldFinish) { + finish(); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java new file mode 100644 index 000000000000..feb24282e0a5 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallStagingFragment.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui.fragments; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import android.widget.ProgressBar; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; + +public class InstallStagingFragment extends DialogFragment { + + private static final String TAG = InstallStagingFragment.class.getSimpleName(); + private ProgressBar mProgressBar; + private AlertDialog mDialog; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null); + dialogView.requireViewById(R.id.staging).setVisibility(View.VISIBLE); + + mDialog = new AlertDialog.Builder(requireContext()) + .setTitle(getString(R.string.app_name_unknown)) + .setIcon(R.drawable.ic_file_download) + .setView(dialogView) + .setNegativeButton(R.string.cancel, null) + .setCancelable(false) + .create(); + + mDialog.setCanceledOnTouchOutside(false); + return mDialog; + } + + @Override + public void onStart() { + super.onStart(); + mDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setEnabled(false); + mProgressBar = mDialog.requireViewById(R.id.progress_indeterminate); + mProgressBar.setProgress(0); + mProgressBar.setMax(100); + mProgressBar.setIndeterminate(false); + } + + public void setProgress(int progress) { + if (mProgressBar != null) { + mProgressBar.setProgress(progress); + } + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java new file mode 100644 index 000000000000..dce0b9abd035 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/SimpleErrorFragment.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.ui.fragments; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import com.android.packageinstaller.R; + +public class SimpleErrorFragment extends DialogFragment { + + private static final String TAG = SimpleErrorFragment.class.getSimpleName(); + private final int mMessageResId; + + public SimpleErrorFragment(int messageResId) { + mMessageResId = messageResId; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getActivity()) + .setMessage(mMessageResId) + .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish()) + .create(); + } + + @Override + public void onCancel(DialogInterface dialog) { + getActivity().setResult(Activity.RESULT_CANCELED); + getActivity().finish(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java new file mode 100644 index 000000000000..42b30234288d --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.viewmodel; + +import android.app.Application; +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.MediatorLiveData; +import androidx.lifecycle.MutableLiveData; +import com.android.packageinstaller.v2.model.InstallRepository; +import com.android.packageinstaller.v2.model.InstallRepository.CallerInfo; +import com.android.packageinstaller.v2.model.installstagedata.InstallStage; +import com.android.packageinstaller.v2.model.installstagedata.InstallStaging; + + +public class InstallViewModel extends AndroidViewModel { + + private static final String TAG = InstallViewModel.class.getSimpleName(); + private final InstallRepository mRepository; + private final MediatorLiveData<InstallStage> mCurrentInstallStage = new MediatorLiveData<>( + new InstallStaging()); + + public InstallViewModel(@NonNull Application application, InstallRepository repository) { + super(application); + mRepository = repository; + } + + public MutableLiveData<InstallStage> getCurrentInstallStage() { + return mCurrentInstallStage; + } + + public void preprocessIntent(Intent intent, CallerInfo callerInfo) { + InstallStage stage = mRepository.performPreInstallChecks(intent, callerInfo); + if (stage.getStageCode() == InstallStage.STAGE_ABORTED) { + mCurrentInstallStage.setValue(stage); + } else { + // Since staging is an async operation, we will get the staging result later in time. + // Result of the file staging will be set in InstallRepository#mStagingResult. + // As such, mCurrentInstallStage will need to add another MutableLiveData + // as a data source + mRepository.stageForInstall(); + mCurrentInstallStage.addSource(mRepository.getStagingResult(), installStage -> { + if (installStage.getStageCode() != InstallStage.STAGE_READY) { + mCurrentInstallStage.setValue(installStage); + } else { + // Proceed with user confirmation here. + } + }); + } + } + + public MutableLiveData<Integer> getStagingProgress() { + return mRepository.getStagingProgress(); + } +} diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java new file mode 100644 index 000000000000..ef459e64d7d5 --- /dev/null +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModelFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.v2.viewmodel; + +import android.app.Application; +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import com.android.packageinstaller.v2.model.InstallRepository; + +public class InstallViewModelFactory extends ViewModelProvider.AndroidViewModelFactory { + + private final InstallRepository mRepository; + private final Application mApplication; + + public InstallViewModelFactory(Application application, InstallRepository repository) { + // Calling super class' ctor ensures that create method is called correctly and the right + // ctor of InstallViewModel is used. If we fail to do that, the default ctor: + // InstallViewModel(application) is used, and repository isn't initialized in the viewmodel + super(application); + mApplication = application; + mRepository = repository; + } + + @NonNull + @Override + @SuppressWarnings("unchecked") + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + return (T) new InstallViewModel(mApplication, mRepository); + } +} diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch.xml deleted file mode 100644 index 1ffdad47bd3c..000000000000 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<Switch - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/switch_widget" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:clickable="false" - android:focusable="false" - android:theme="@style/Switch.SettingsLib" /> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml index 1054e0017cf1..e3f8fbb88a65 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml @@ -47,7 +47,14 @@ android:textAppearance="?android:attr/textAppearanceListItem" style="@style/MainSwitchText.Settingslib" /> - <include layout="@layout/settingslib_main_switch" /> + <Switch + android:id="@android:id/switch_widget" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_gravity="center_vertical" + android:focusable="false" + android:clickable="false" + android:theme="@style/Switch.SettingsLib"/> </LinearLayout> </LinearLayout> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml index 4a0e7b377412..255b2c92e709 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml @@ -49,7 +49,14 @@ android:lineBreakWordStyle="phrase" style="@style/MainSwitchText.Settingslib" /> - <include layout="@layout/settingslib_main_switch" /> + <Switch + android:id="@android:id/switch_widget" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_gravity="center_vertical" + android:focusable="false" + android:clickable="false" + android:theme="@style/Switch.SettingsLib"/> </LinearLayout> </LinearLayout> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch.xml deleted file mode 100644 index 12c1d76e9ae4..000000000000 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<Switch - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/switch_widget" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clickable="false" - android:focusable="false" - android:theme="@style/SwitchBar.Switch.Settingslib" /> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml index fe64fea2d7cc..bf34db93298b 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml @@ -38,6 +38,13 @@ android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start" android:textAlignment="viewStart"/> - <include layout="@layout/settingslib_main_switch" /> + <Switch + android:id="@android:id/switch_widget" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:focusable="false" + android:clickable="false" + android:theme="@style/SwitchBar.Switch.Settingslib"/> </LinearLayout> diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index fdb0471755de..905640f1ca4b 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,7 +15,7 @@ # [versions] -agp = "8.1.2" +agp = "8.1.3" compose-compiler = "1.5.1" dexmaker-mockito = "2.28.3" jvm = "17" diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp index bd508cbb9e2f..dbf4ce047825 100644 --- a/packages/SettingsLib/Spa/screenshot/Android.bp +++ b/packages/SettingsLib/Spa/screenshot/Android.bp @@ -18,6 +18,14 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +filegroup { + name: "SpaScreenshotTestRNGFiles", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], +} + android_test { name: "SpaScreenshotTests", use_resource_processor: true, @@ -36,6 +44,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.runner", "mockito-target-minus-junit4", + "platform-parametric-runner-lib", "platform-screenshot-diff-core", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SettingsLib/Spa/screenshot/robotests/Android.bp b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp new file mode 100644 index 000000000000..6b8197c03e75 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/Android.bp @@ -0,0 +1,74 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_library { + name: "SpaRoboRNGTestsAssetsLib", + asset_dirs: ["assets"], + sdk_version: "current", + platform_apis: true, + manifest: "AndroidManifest.xml", + optimize: { + enabled: false, + }, + use_resource_processor: true, + resource_dirs: ["res"], +} + +android_app { + name: "SpaRoboApp", + srcs: [], + static_libs: [ + "androidx.test.espresso.core", + "androidx.appcompat_appcompat", + "flag-junit", + "guava", + "SpaLib", + "SpaLibTestUtils", + "SpaRoboRNGTestsAssetsLib", + "platform-screenshot-diff-core", + "PlatformComposeSceneTransitionLayoutTestsUtils", + ], + manifest: "robo-manifest.xml", + aaptflags: [ + "--extra-packages", + "com.android.settingslib.spa.screenshot", + ], + dont_merge_manifests: true, + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + resource_dirs: [], + kotlincflags: ["-Xjvm-default=all"], + + plugins: ["dagger2-compiler"], + use_resource_processor: true, +} + +android_robolectric_test { + name: "SpaRoboRNGTests", + srcs: [ + ":SpaScreenshotTestRNGFiles", + ":flag-junit", + ":platform-test-screenshot-rules", + ], + // Do not add any new libraries here, they should be added to SpaRoboApp above. + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.test.uiautomator_uiautomator", + "androidx.test.ext.junit", + "inline-mockito-robolectric-prebuilt", + "platform-parametric-runner-lib", + "uiautomator-helpers", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "truth", + ], + upstream: true, + java_resource_dirs: ["config"], + instrumentation_for: "SpaRoboApp", +} diff --git a/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml new file mode 100644 index 000000000000..1918e1c9b5c7 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.spa.screenshot"> + + <uses-sdk android:minSdkVersion="21"/> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> +</manifest> diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png Binary files differnew file mode 100644 index 000000000000..b2f3cf1fe737 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_actionButtons.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png Binary files differnew file mode 100644 index 000000000000..dc7e756f08e3 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_barChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png Binary files differnew file mode 100644 index 000000000000..95021fade418 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_footer.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png Binary files differnew file mode 100644 index 000000000000..281f572a7307 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_imageIllustration.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png Binary files differnew file mode 100644 index 000000000000..0dc707f9cf2f --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_lineChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..7c135a09d588 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png Binary files differnew file mode 100644 index 000000000000..7c3e993b0fa4 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_pieChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png Binary files differnew file mode 100644 index 000000000000..7b438c40134c --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png Binary files differnew file mode 100644 index 000000000000..ac64619057b1 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_progressBar.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png Binary files differnew file mode 100644 index 000000000000..5506c8c7854a --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png Binary files differnew file mode 100644 index 000000000000..bb518c0f1cc5 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_spinner.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png Binary files differnew file mode 100644 index 000000000000..f4b90634b536 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_switchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..fc60c0f43513 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_twoTargetSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png Binary files differnew file mode 100644 index 000000000000..fa069275dbd4 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_actionButtons.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png Binary files differnew file mode 100644 index 000000000000..698cb4edd199 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_barChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png Binary files differnew file mode 100644 index 000000000000..95021fade418 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_footer.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png Binary files differnew file mode 100644 index 000000000000..a5f3fa5063d6 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_imageIllustration.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png Binary files differnew file mode 100644 index 000000000000..c1938b4adf01 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_lineChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..81a181f96f17 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png Binary files differnew file mode 100644 index 000000000000..ffc6729c73bb --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_pieChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..a6200400687e --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png Binary files differnew file mode 100644 index 000000000000..0b40aa872764 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_progressBar.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png Binary files differnew file mode 100644 index 000000000000..cfe8587e852b --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png Binary files differnew file mode 100644 index 000000000000..bb518c0f1cc5 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_spinner.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png Binary files differnew file mode 100644 index 000000000000..363275527101 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_switchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..7e5b602e8adb --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_twoTargetSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png Binary files differnew file mode 100644 index 000000000000..a3692cdc92f9 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_actionButtons.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png Binary files differnew file mode 100644 index 000000000000..233d088cf890 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_barChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png Binary files differnew file mode 100644 index 000000000000..10869f258b3b --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_footer.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png Binary files differnew file mode 100644 index 000000000000..3eaecc14935f --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_imageIllustration.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png Binary files differnew file mode 100644 index 000000000000..ca619117cbf4 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_lineChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..8a0da3130281 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_mainSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png Binary files differnew file mode 100644 index 000000000000..19d0afd6618a --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_pieChart.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png Binary files differnew file mode 100644 index 000000000000..236a1a0234d1 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_preference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png Binary files differnew file mode 100644 index 000000000000..72b7954b9972 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_progressBar.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png Binary files differnew file mode 100644 index 000000000000..36486c44198a --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png Binary files differnew file mode 100644 index 000000000000..5bb318fa0a91 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_spinner.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png Binary files differnew file mode 100644 index 000000000000..fedce448e970 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_switchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png Binary files differnew file mode 100644 index 000000000000..3b389d77b04d --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_twoTargetSwitchPreference.png diff --git a/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties new file mode 100644 index 000000000000..83d7549551ce --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/config/robolectric.properties @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml new file mode 100644 index 000000000000..6597ffb57688 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/res/drawable/accessibility_captioning_banner.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="412dp" + android:height="300dp" + android:viewportWidth="412" + android:viewportHeight="300"> + <path + android:pathData="M383.9,300H28.1C12.6,300 0,287.4 0,271.9V28.1C0,12.6 12.6,0 28.1,0h355.8C399.4,0 412,12.6 412,28.1v243.8C412,287.4 399.4,300 383.9,300z" + android:fillColor="#FFFFFF"/> + <path + android:pathData="M79.2,179.6h53.6v8.5h-53.6z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M142.5,179.6h30.4v8.5h-30.4z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M79.2,195.5h79.2v8.5h-79.2z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M168.1,195.5h34.1v8.5h-34.1z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M211.9,195.5h34.1v8.5h-34.1z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M182.7,179.6h73.1v8.5h-73.1z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M265.5,179.6h26.8v8.5h-26.8z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M302.1,179.6h26.8v8.5h-26.8z" + android:fillColor="#1A73E8"/> + <path + android:pathData="M142.7,67.9h-11.5c-1.6,0 -2.9,1.3 -2.9,2.9H67.8c-7.9,0 -14.4,6.5 -14.4,14.4v132.4c0,7.9 6.5,14.4 14.4,14.4h276.4c7.9,0 14.4,-6.5 14.4,-14.4V85.2c0,-7.9 -6.5,-14.4 -14.4,-14.4H203.1c0,-1.6 -1.3,-2.9 -2.9,-2.9h-28.8c-1.6,0 -2.9,1.3 -2.9,2.9h-23C145.5,69.2 144.3,67.9 142.7,67.9zM344.2,73.7c6.4,0 11.5,5.2 11.5,11.5v132.4c0,6.3 -5.2,11.5 -11.5,11.5H67.8c-6.4,0 -11.5,-5.2 -11.5,-11.5V85.2c0,-6.3 5.2,-11.5 11.5,-11.5H344.2z" + android:fillColor="#DADCE0"/> +</vector> diff --git a/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml new file mode 100644 index 000000000000..af1a11ed0a31 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/robotests/robo-manifest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.settingslib.spa.screenshot" + coreApp="true"> + <application> + <activity + android:name="androidx.activity.ComponentActivity" + android:exported="true"> + </activity> + </application> +</manifest> diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt index 3dcefe9c783d..1cbdc33d5a4e 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt @@ -16,6 +16,7 @@ package com.android.settingslib.spa.screenshot.util +import android.os.Build import androidx.activity.ComponentActivity import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -49,15 +50,19 @@ class SettingsScreenshotTestRule( ) ) private val composeRule = createAndroidComposeRule<ComponentActivity>() - private val delegateRule = - RuleChain.outerRule(colorsRule) - .around(deviceEmulationRule) + private val roboRule = + RuleChain.outerRule(deviceEmulationRule) .around(screenshotRule) .around(composeRule) + private val delegateRule = + RuleChain.outerRule(colorsRule) + .around(roboRule) private val matcher = UnitTestBitmapMatcher + private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false override fun apply(base: Statement, description: Description): Statement { - return delegateRule.apply(base, description) + val ruleToApply = if (isRobolectric) roboRule else delegateRule + return ruleToApply.apply(base, description) } /** diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt index b74a2430c605..2cb6044d9bb9 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/ActionButtonsScreenshotTest.kt @@ -26,15 +26,16 @@ import com.android.settingslib.spa.widget.button.ActionButtons import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class ActionButtonsScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java new file mode 100644 index 000000000000..8e55695bc032 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/button/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.settingslib.spa.screenshot.widget.button; + +import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt index 051ef7733a69..7ef9f106a082 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt @@ -25,15 +25,16 @@ import com.github.mikephil.charting.formatter.IAxisValueFormatter import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt index 3822571758fc..3790164612c0 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/LineChartScreenshotTest.kt @@ -25,15 +25,16 @@ import java.text.NumberFormat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class LineChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt index 6dd62ec03257..3c3cc85b135d 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/PieChartScreenshotTest.kt @@ -23,15 +23,16 @@ import com.android.settingslib.spa.widget.chart.PieChartModel import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class PieChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java new file mode 100644 index 000000000000..afe3f07a492e --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.settingslib.spa.screenshot.widget.chart; + +import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt index 0ccfc0b12fca..616b22525b09 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/ImageIllustrationScreenshotTest.kt @@ -24,15 +24,16 @@ import com.android.settingslib.spa.widget.illustration.ResourceType import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class ImageIllustrationScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java new file mode 100644 index 000000000000..0089c2e26e34 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/illustration/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.settingslib.spa.screenshot.widget.illustration; + +import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt index e547d260cbdb..8dd4ce7fec2f 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/MainSwitchPreferenceScreenshotTest.kt @@ -23,15 +23,16 @@ import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class MainSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt index dd6b553f714d..1e1a785bea21 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/PreferenceScreenshotTest.kt @@ -28,15 +28,16 @@ import com.android.settingslib.spa.widget.ui.SettingsIcon import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal private const val TITLE = "Title" diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt index 357d81593cb6..d1878a744882 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/ProgressBarPreferenceScreenshotTest.kt @@ -29,15 +29,16 @@ import com.android.settingslib.spa.widget.ui.CircularProgressBar import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class ProgressBarPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt index fdee7ee70483..c9f098bd77e3 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SliderPreferenceScreenshotTest.kt @@ -25,15 +25,16 @@ import com.android.settingslib.spa.widget.preference.SliderPreferenceModel import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class SliderPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt index 6966a74b0af5..eca40fbfee45 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/SwitchPreferenceScreenshotTest.kt @@ -27,15 +27,16 @@ import com.android.settingslib.spa.widget.ui.SettingsIcon import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class SwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt index 72c5cb81962e..f81a59fef79c 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/TwoTargetSwitchPreferenceScreenshotTest.kt @@ -24,15 +24,16 @@ import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class TwoTargetSwitchPreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java new file mode 100644 index 000000000000..fd6a5dda5ca8 --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/preference/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.settingslib.spa.screenshot.widget.preference; + +import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt index fb01f7715def..98a4288d7e53 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/FooterScreenshotTest.kt @@ -21,15 +21,16 @@ import com.android.settingslib.spa.widget.ui.Footer import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class FooterScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt index 2867741e2e0a..5417095260f3 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/SpinnerScreenshotTest.kt @@ -22,15 +22,16 @@ import com.android.settingslib.spa.widget.ui.SpinnerOption import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.PhoneAndTabletMinimal /** A screenshot test for ExampleFeature. */ -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) class SpinnerScreenshotTest(emulationSpec: DeviceEmulationSpec) { companion object { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal } diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java new file mode 100644 index 000000000000..45210abad88e --- /dev/null +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/ui/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +@GraphicsMode(GraphicsMode.Mode.NATIVE) +package com.android.settingslib.spa.screenshot.widget.ui; + +import org.robolectric.annotation.GraphicsMode; diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 47660bc44cc8..5a1120e6c7a1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.ui.unit.dp object SettingsDimension { + val paddingTiny = 2.dp val paddingSmall = 4.dp val itemIconSize = 24.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index f4b284362c52..b4a6a0d00720 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -39,6 +39,7 @@ import com.android.settingslib.spa.framework.theme.toMediumWeight fun SettingsTitle(title: String, useMediumWeight: Boolean = false) { Text( text = title, + modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight), ) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt new file mode 100644 index 000000000000..d0d2dc0083a6 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt @@ -0,0 +1,42 @@ +/* + * 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.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn + +/** + * A [BroadcastReceiver] flow for the given [intentFilter]. + */ +fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow { + val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + trySend(intent) + } + } + registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED) + + awaitClose { unregisterReceiver(broadcastReceiver) } +}.conflate().flowOn(Dispatchers.Default) diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt new file mode 100644 index 000000000000..dfaf3c66ff8d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt @@ -0,0 +1,80 @@ +/* + * 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.common + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock + +@RunWith(AndroidJUnit4::class) +class BroadcastReceiverFlowTest { + + private var registeredBroadcastReceiver: BroadcastReceiver? = null + + private val context = mock<Context> { + on { + registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED)) + } doAnswer { + registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver + null + } + } + + @Test + fun broadcastReceiverFlow_registered() = runBlocking { + val flow = context.broadcastReceiverFlow(INTENT_FILTER) + + flow.firstWithTimeoutOrNull() + + assertThat(registeredBroadcastReceiver).isNotNull() + } + + @Test + fun broadcastReceiverFlow_isCalledOnReceive() = runBlocking { + var onReceiveIsCalled = false + launch { + context.broadcastReceiverFlow(INTENT_FILTER).first { + onReceiveIsCalled = true + true + } + } + + delay(100) + registeredBroadcastReceiver!!.onReceive(context, Intent()) + delay(100) + + assertThat(onReceiveIsCalled).isTrue() + } + + private companion object { + val INTENT_FILTER = IntentFilter() + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index fa8c1fba6780..0ffcc45b6466 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -594,6 +594,16 @@ public class BluetoothUtils { || cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO); } + /** + * Check if the Bluetooth device is an active LE Audio device + * + * @param cachedDevice the CachedBluetoothDevice + * @return if the Bluetooth device is an active LE Audio device + */ + public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) { + return cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO); + } + private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) { if (cachedDevice == null) { return false; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index e9f4b5c8efc6..245fe6edd601 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1310,7 +1310,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { stringRes = R.string.bluetooth_battery_level_untethered; - } else if (batteryLevelPercentageString != null) { + } else if (batteryLevelPercentageString != null && !shortSummary) { stringRes = R.string.bluetooth_battery_level; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 7e8fe7e09d74..d9fe7335dbcb 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -445,5 +445,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 2e174e267bde..f1b53edbb1cd 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -233,7 +233,6 @@ public class SettingsBackupTest { Settings.Global.DEVELOPMENT_FORCE_RTL, Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, - Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, Settings.Global.DEVICE_DEMO_MODE, Settings.Global.DISABLE_WINDOW_BLURS, @@ -686,7 +685,8 @@ public class SettingsBackupTest { Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED, Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, - Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED); + Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, + Settings.Global.Wearable.CONNECTIVITY_KEEP_DATA_ON); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 0e9f8b153fd4..cf51e2193833 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -246,11 +246,9 @@ filegroup { srcs: [ /* Status bar fakes */ "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt", - "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt", "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt", "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt", + "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt", /* QS fakes */ "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt", @@ -263,6 +261,7 @@ filegroup { srcs: [ /* Keyguard converted tests */ // data + "tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt", "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt", "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt", "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt", @@ -285,6 +284,7 @@ filegroup { "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt", "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt", "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt", + "tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt", "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt", "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt", "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt", @@ -294,8 +294,6 @@ filegroup { "tests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt", - "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt", // Keyguard helper diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index aebdaaba3ce1..e340209afbb8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -107,8 +107,16 @@ flag { } flag { - name: "qs_new_pipeline" - namespace: "systemui" - description: "Use the new pipeline for Quick Settings. Should have no behavior changes." - bug: "241772429" + name: "qs_new_pipeline" + namespace: "systemui" + description: "Use the new pipeline for Quick Settings. Should have no behavior changes." + bug: "241772429" } + +flag { + name: "coroutine_tracing" + namespace: "systemui" + description: "Adds thread-local data to System UI's global coroutine scopes to " + "allow for tracing of coroutine continuations using System UI's tracinglib" + bug: "289353932" +}
\ No newline at end of file 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 39287674be0e..7eb7dacda255 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 @@ -208,12 +208,8 @@ private fun StandardLayout( val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired - val currentSceneKey by - remember(isSplitAroundTheFold) { - mutableStateOf( - if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey - ) - } + val currentSceneKey = + if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey SceneTransitionLayout( currentScene = currentSceneKey, @@ -405,8 +401,7 @@ private fun UserInputArea( if (visibility == UserInputAreaVisibility.INPUT_ONLY) { PatternBouncer( viewModel = nonNullViewModel, - modifier = - Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false).then(modifier) + modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false) ) } else -> Unit diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index df22a7023ebf..0b1338305076 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -63,11 +64,13 @@ internal fun PasswordBouncer( val isImeVisible by rememberUpdatedState(WindowInsets.imeAnimationTarget.getBottom(density) > 0) LaunchedEffect(isImeVisible) { viewModel.onImeVisibilityChanged(isImeVisible) } - LaunchedEffect(Unit) { + DisposableEffect(Unit) { + viewModel.onShown() + // When the UI comes up, request focus on the TextField to bring up the software keyboard. focusRequester.requestFocus() - // Also, report that the UI is shown to let the view-model runs some logic. - viewModel.onShown() + + onDispose { viewModel.onHidden() } } LaunchedEffect(animateFailure) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 03efbe0fe1ff..2bbe9b8fc20a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -65,8 +66,10 @@ internal fun PatternBouncer( viewModel: PatternBouncerViewModel, modifier: Modifier = Modifier, ) { - // Report that the UI is shown to let the view-model run some logic. - LaunchedEffect(Unit) { viewModel.onShown() } + DisposableEffect(Unit) { + viewModel.onShown() + onDispose { viewModel.onHidden() } + } val colCount = viewModel.columnCount val rowCount = viewModel.rowCount diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index fb50f69f7d9b..59617c9022ab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.sizeIn import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -69,13 +70,17 @@ fun PinPad( viewModel: PinBouncerViewModel, modifier: Modifier = Modifier, ) { - // Report that the UI is shown to let the view-model run some logic. - LaunchedEffect(Unit) { viewModel.onShown() } + DisposableEffect(Unit) { + viewModel.onShown() + onDispose { viewModel.onHidden() } + } val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isDigitButtonAnimationEnabled: Boolean by + viewModel.isDigitButtonAnimationEnabled.collectAsState() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { @@ -94,10 +99,11 @@ fun PinPad( ) { repeat(9) { index -> DigitButton( - index + 1, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[index]::value, + digit = index + 1, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[index]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) } @@ -116,10 +122,11 @@ fun PinPad( ) DigitButton( - 0, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[10]::value, + digit = 0, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[10]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) ActionButton( @@ -143,15 +150,17 @@ private fun DigitButton( isInputEnabled: Boolean, onClicked: (Int) -> Unit, scaling: () -> Float, + isAnimationEnabled: Boolean, ) { PinPadButton( onClicked = { onClicked(digit) }, isEnabled = isInputEnabled, backgroundColor = MaterialTheme.colorScheme.surfaceVariant, foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant, + isAnimationEnabled = isAnimationEnabled, modifier = Modifier.graphicsLayer { - val scale = scaling() + val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale } @@ -195,6 +204,7 @@ private fun ActionButton( isEnabled = isInputEnabled && !isHidden, backgroundColor = backgroundColor, foregroundColor = foregroundColor, + isAnimationEnabled = true, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha @@ -216,6 +226,7 @@ private fun PinPadButton( isEnabled: Boolean, backgroundColor: Color, foregroundColor: Color, + isAnimationEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, @@ -243,7 +254,7 @@ private fun PinPadButton( val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else pinButtonSize / 2, + if (isAnimationEnabled && isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", animationSpec = tween(animDurationMillis, easing = animEasing) ) @@ -251,7 +262,7 @@ private fun PinPadButton( val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.primary else -> backgroundColor }, label = "Pin button container color", @@ -260,7 +271,7 @@ private fun PinPadButton( val contentColor = animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.onPrimary else -> foregroundColor }, label = "Pin button container color", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt index 814ea31ad510..1a97912c77bb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt @@ -18,6 +18,11 @@ package com.android.systemui.bouncer.ui.composable +import android.app.AlertDialog +import android.app.Dialog +import android.view.Gravity +import android.view.WindowManager +import android.widget.TextView import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.VectorConverter import androidx.compose.animation.core.tween @@ -26,11 +31,16 @@ import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -41,14 +51,21 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.android.compose.PlatformOutlinedButton import com.android.compose.animation.Easings import com.android.keyguard.PinShapeAdapter import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit @@ -189,6 +206,10 @@ private fun RegularPinInputDisplay( shapeAnimations: ShapeAnimations, modifier: Modifier = Modifier, ) { + if (viewModel.isSimAreaVisible) { + SimArea(viewModel = viewModel) + } + // Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from // `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove // animation, thus the composable to be removed has to remain in the composition until fully @@ -234,6 +255,94 @@ private fun RegularPinInputDisplay( pinInputRow.Content(modifier) } +@Composable +private fun SimArea(viewModel: PinBouncerViewModel) { + val isLockedEsim by viewModel.isLockedEsim.collectAsState() + val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState() + val errorDialogMessage by viewModel.errorDialogMessage.collectAsState() + var unlockDialog: Dialog? by remember { mutableStateOf(null) } + var errorDialog: Dialog? by remember { mutableStateOf(null) } + val context = LocalView.current.context + + DisposableEffect(isSimUnlockingDialogVisible) { + if (isSimUnlockingDialogVisible) { + val builder = + AlertDialog.Builder(context).apply { + setMessage(context.getString(R.string.kg_sim_unlock_progress_dialog_message)) + setCancelable(false) + } + unlockDialog = + builder.create().apply { + window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) + show() + findViewById<TextView>(android.R.id.message)?.gravity = Gravity.CENTER + } + } else { + unlockDialog?.hide() + unlockDialog = null + } + + onDispose { + unlockDialog?.hide() + unlockDialog = null + } + } + + DisposableEffect(errorDialogMessage) { + if (errorDialogMessage != null) { + val builder = AlertDialog.Builder(context) + builder.setMessage(errorDialogMessage) + builder.setCancelable(false) + builder.setNeutralButton(R.string.ok, null) + errorDialog = + builder.create().apply { + window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) + setOnDismissListener { viewModel.onErrorDialogDismissed() } + show() + } + } else { + errorDialog?.hide() + errorDialog = null + } + + onDispose { + errorDialog?.hide() + errorDialog = null + } + } + + Box(modifier = Modifier.padding(bottom = 20.dp)) { + // If isLockedEsim is null, then we do not show anything. + if (isLockedEsim == true) { + PlatformOutlinedButton( + onClick = { viewModel.onDisableEsimButtonClicked() }, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.ic_no_sim), + contentDescription = null, + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) + ) + Text( + text = stringResource(R.string.disable_carrier_button_text), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + } + } else if (isLockedEsim == false) { + Image( + painter = painterResource(id = R.drawable.ic_lockscreen_sim), + contentDescription = null, + colorFilter = ColorFilter.tint(colorResource(id = R.color.background_protected)) + ) + } + } +} + private class PinInputRow( val shapeAnimations: ShapeAnimations, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 37804682ed98..09706bed1921 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -12,7 +12,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -25,10 +24,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.transitions import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -76,17 +77,24 @@ fun CommunalContainer( currentScene = currentScene, onChangeScene = { sceneKey -> viewModel.onSceneChanged(sceneKey.toCommunalSceneKey()) }, transitions = sceneTransitions, + edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize) ) { scene( TransitionSceneKey.Blank, - userActions = mapOf(Swipe.Left to TransitionSceneKey.Communal) + userActions = + mapOf( + Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal + ) ) { BlankScene { showSceneTransitionLayout = false } } scene( TransitionSceneKey.Communal, - userActions = mapOf(Swipe.Right to TransitionSceneKey.Blank), + userActions = + mapOf( + Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank + ), ) { CommunalScene(viewModel, modifier = modifier) } @@ -105,14 +113,12 @@ private fun BlankScene( Box(modifier.fillMaxSize()) { Column( Modifier.fillMaxHeight() - .width(100.dp) + .width(ContainerDimensions.EdgeSwipeSize) .align(Alignment.CenterEnd) .background(Color(0x55e9f2eb)), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text("Default scene") - IconButton(onClick = hideSceneTransitionLayout) { Icon(Icons.Filled.Close, contentDescription = "Close button") } @@ -142,3 +148,7 @@ fun CommunalSceneKey.toTransitionSceneKey(): SceneKey { fun SceneKey.toCommunalSceneKey(): CommunalSceneKey { return this.identity as CommunalSceneKey } + +object ContainerDimensions { + val EdgeSwipeSize = 40.dp +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 142978284cfb..6e18cb9cd46b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -22,10 +22,11 @@ import android.widget.FrameLayout import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan @@ -42,6 +43,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -65,6 +67,7 @@ fun CommunalHub( LazyHorizontalGrid( modifier = modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart), rows = GridCells.Fixed(CommunalContentSize.FULL.span), + contentPadding = PaddingValues(horizontal = Dimensions.Spacing), horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing), verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing), ) { @@ -92,6 +95,16 @@ fun CommunalHub( LocalContext.current.getString(R.string.button_to_open_widget_picker) ) } + + // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving + // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge + // swipe back to the blank scene. + Spacer( + Modifier.height(Dimensions.GridHeight) + .align(Alignment.CenterStart) + .width(Dimensions.Spacing) + .pointerInput(Unit) {} + ) } } @@ -164,11 +177,7 @@ private fun TutorialContent(modifier: Modifier = Modifier) { @Composable private fun Umo(viewModel: CommunalViewModel, modifier: Modifier = Modifier) { AndroidView( - modifier = - modifier - .width(Dimensions.CardWidth) - .height(Dimensions.CardHeightThird) - .padding(Dimensions.Spacing), + modifier = modifier, factory = { viewModel.mediaHost.expansion = MediaHostState.EXPANDED viewModel.mediaHost.showsOnlyActiveMedia = false diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt deleted file mode 100644 index c84a5e91ca50..000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.qs.footer.ui.compose - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.defaultMinSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp -import com.android.compose.animation.scene.ElementKey -import com.android.compose.animation.scene.SceneScope - -object QuickSettings { - object Elements { - // TODO RENAME - val Content = ElementKey("QuickSettingsContent") - val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid") - val FooterActions = ElementKey("QuickSettingsFooterActions") - } -} - -@Composable -fun SceneScope.QuickSettings( - modifier: Modifier = Modifier, -) { - // TODO(b/272780058): implement. - Column( - modifier = - modifier - .element(QuickSettings.Elements.Content) - .fillMaxWidth() - .defaultMinSize(minHeight = 300.dp) - .clip(RoundedCornerShape(32.dp)) - .background(MaterialTheme.colorScheme.primary) - .padding(16.dp), - ) { - Text( - text = "Quick settings grid", - modifier = - Modifier.element(QuickSettings.Elements.CollapsedGrid) - .align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onPrimary, - ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "QS footer actions", - modifier = - Modifier.element(QuickSettings.Elements.FooterActions) - .align(Alignment.CenterHorizontally), - style = MaterialTheme.typography.titleSmall, - color = MaterialTheme.colorScheme.onPrimary, - ) - } -} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt new file mode 100644 index 000000000000..28a4801d582a --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.composable + +import android.view.ContextThemeWrapper +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.SceneScope +import com.android.compose.theme.colorAttr +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.res.R + +object QuickSettings { + object Elements { + // TODO RENAME + val Content = ElementKey("QuickSettingsContent") + val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid") + val FooterActions = ElementKey("QuickSettingsFooterActions") + } +} + +@Composable +private fun QuickSettingsTheme(content: @Composable () -> Unit) { + val context = LocalContext.current + val themedContext = + remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) } + CompositionLocalProvider(LocalContext provides themedContext) { content() } +} + +@Composable +fun SceneScope.QuickSettings( + modifier: Modifier = Modifier, + qsSceneAdapter: QSSceneAdapter, + state: QSSceneAdapter.State +) { + // TODO(b/272780058): implement. + Column( + modifier = + modifier + .element(QuickSettings.Elements.Content) + .fillMaxWidth() + .defaultMinSize(minHeight = 300.dp) + .clip(RoundedCornerShape(32.dp)) + .background(MaterialTheme.colorScheme.primary) + .padding(1.dp), + ) { + QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state) + } +} + +@Composable +private fun QuickSettingsContent( + qsSceneAdapter: QSSceneAdapter, + state: QSSceneAdapter.State, + modifier: Modifier = Modifier, +) { + val qsView by qsSceneAdapter.qsView.collectAsState(null) + QuickSettingsTheme { + val context = LocalContext.current + + val frame by remember(context) { mutableStateOf(FrameLayout(context)) } + + LaunchedEffect(key1 = context) { + if (qsView == null) { + qsSceneAdapter.inflate(context, frame) + } + } + qsView?.let { + it.attachToParent(frame) + AndroidView( + modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)), + factory = { _ -> + qsSceneAdapter.setState(state) + frame + }, + onRelease = { frame.removeAllViews() }, + update = { qsSceneAdapter.setState(state) } + ) + } + } +} + +private fun View.attachToParent(parent: ViewGroup) { + if (this.parent != null && this.parent != parent) { + (this.parent as ViewGroup).removeView(this) + } + if (this.parent != parent) { + parent.addView( + this, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index a33eac55ac3e..b9451d1c1585 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -17,43 +17,53 @@ package com.android.systemui.qs.ui.composable import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel -import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.scene.shared.model.SceneModel -import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader +import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager import com.android.systemui.statusbar.phone.StatusBarLocation import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn /** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */ @SysUISingleton class QuickSettingsScene @Inject constructor( + @Application private val applicationScope: CoroutineScope, private val viewModel: QuickSettingsSceneViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, @@ -61,14 +71,12 @@ constructor( ) : ComposableScene { override val key = SceneKey.QuickSettings - private val _destinationScenes = - MutableStateFlow<Map<UserAction, SceneModel>>( - mapOf( - UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), - ) - ) - .asStateFlow() - override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes + override val destinationScenes = + viewModel.destinationScenes.stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = emptyMap(), + ) @Composable override fun SceneScope.Content( @@ -93,6 +101,9 @@ private fun SceneScope.QuickSettingsScene( modifier: Modifier = Modifier, ) { // TODO(b/280887232): implement the real UI. + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val collapsedHeaderHeight = + with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = @@ -103,12 +114,27 @@ private fun SceneScope.QuickSettingsScene( ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> - ExpandedShadeHeader( - viewModel = viewModel.shadeHeaderViewModel, - createTintedIconManager = createTintedIconManager, - createBatteryMeterViewController = createBatteryMeterViewController, - statusBarIconController = statusBarIconController, - ) + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(1000), + initialHeight = { collapsedHeaderHeight }, + ) + fadeIn(tween(1000)), + exit = + shrinkVertically( + animationSpec = tween(1000), + targetHeight = { collapsedHeaderHeight }, + shrinkTowards = Alignment.Top, + ) + fadeOut(tween(1000)), + ) { + ExpandedShadeHeader( + viewModel = viewModel.shadeHeaderViewModel, + createTintedIconManager = createTintedIconManager, + createBatteryMeterViewController = createBatteryMeterViewController, + statusBarIconController = statusBarIconController, + ) + } else -> CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, @@ -118,6 +144,10 @@ private fun SceneScope.QuickSettingsScene( ) } Spacer(modifier = Modifier.height(16.dp)) - QuickSettings() + QuickSettings( + modifier = Modifier.fillMaxHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QS + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 0da562bcb3bb..4eb9089dc589 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -18,7 +18,6 @@ package com.android.systemui.scene.ui.composable -import android.os.SystemProperties import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text @@ -84,7 +83,6 @@ fun SceneContainer( val currentDestinations: Map<UserAction, SceneModel> by currentScene.destinationScenes.collectAsState() val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) } - val isRibbonEnabled = remember { SystemProperties.getBoolean("flexi.ribbon", false) } DisposableEffect(viewModel, state) { viewModel.setTransitionState(state.observableTransitionState().map { it.toModel() }) @@ -137,17 +135,15 @@ fun SceneContainer( } } - if (isRibbonEnabled) { - BottomRightCornerRibbon( - content = { - Text( - text = "flexi\uD83E\uDD43", - color = Color.White, - ) - }, - modifier = Modifier.align(Alignment.BottomEnd), - ) - } + BottomRightCornerRibbon( + content = { + Text( + text = "flexi\uD83E\uDD43", + color = Color.White, + ) + }, + modifier = Modifier.align(Alignment.BottomEnd), + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt index 7ecfb62c4f62..fadbdce80cbf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt @@ -4,7 +4,7 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.Shade fun TransitionBuilder.lockscreenToShadeTransition() { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt index be85beea6ee0..5616175ed11c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt @@ -4,7 +4,7 @@ import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.shadeToQuickSettingsTransition() { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 13ebdf9c4a7c..a02f046a18f5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -37,7 +38,8 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.qs.footer.ui.compose.QuickSettings +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -152,7 +154,11 @@ private fun SceneScope.ShadeScene( statusBarIconController = statusBarIconController, ) Spacer(modifier = Modifier.height(16.dp)) - QuickSettings(modifier = Modifier.height(160.dp)) + QuickSettings( + modifier = Modifier.wrapContentHeight(), + viewModel.qsSceneAdapter, + QSSceneAdapter.State.QQS + ) Spacer(modifier = Modifier.height(16.dp)) Notifications(modifier = Modifier.weight(1f)) } diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp index 050d1d5651ad..3424085049cc 100644 --- a/packages/SystemUI/compose/scene/Android.bp +++ b/packages/SystemUI/compose/scene/Android.bp @@ -21,12 +21,19 @@ package { default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], } +filegroup { + name: "PlatformComposeSceneTransitionLayout-srcs", + srcs: [ + "src/**/*.kt", + ], +} + android_library { name: "PlatformComposeSceneTransitionLayout", manifest: "AndroidManifest.xml", srcs: [ - "src/**/*.kt", + ":PlatformComposeSceneTransitionLayout-srcs", ], static_libs: [ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index b77a60b5b5d6..3b999e304491 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -17,38 +17,33 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isSpecified import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.geometry.lerp import androidx.compose.ui.graphics.drawscope.scale -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.IntermediateMeasureScope import androidx.compose.ui.layout.Measurable import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.round import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation -import com.android.compose.modifiers.thenIf import com.android.compose.ui.util.lerp +import kotlinx.coroutines.launch /** An element on screen, that can be composed in one or more scenes. */ internal class Element(val key: ElementKey) { @@ -95,13 +90,20 @@ internal class Element(val key: ElementKey) { } /** The target values of this element in a given scene. */ - class TargetValues { + class TargetValues(val scene: SceneKey) { val lastValues = Values() var targetSize by mutableStateOf(SizeUnspecified) var targetOffset by mutableStateOf(Offset.Unspecified) val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>() + + /** + * The attached [ElementNode] a Modifier.element() for a given element and scene. During + * composition, this set could have 0 to 2 elements. After composition and after all + * modifier nodes have been attached/detached, this set should contain exactly 1 element. + */ + val nodes = mutableSetOf<ElementNode>() } /** A shared value of this element. */ @@ -128,64 +130,31 @@ internal fun Modifier.element( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, key: ElementKey, -): Modifier = composed { - val sceneValues = remember(scene, key) { Element.TargetValues() } - val element = - // Get the element associated to [key] if it was already composed in another scene, - // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a - // withoutReadObservation() because there is no need to recompose when that map is mutated. - Snapshot.withoutReadObservation { - val element = - layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it } - val previousValues = element.sceneValues[scene.key] - if (previousValues == null) { - element.sceneValues[scene.key] = sceneValues - } else if (previousValues != sceneValues) { - error("$key was composed multiple times in $scene") - } - - element - } - val lastSharedValues = element.lastSharedValues - val lastSceneValues = sceneValues.lastValues - - DisposableEffect(scene, sceneValues, element) { - onDispose { - element.sceneValues.remove(scene.key) - - // This was the last scene this element was in, so remove it from the map. - if (element.sceneValues.isEmpty()) { - layoutImpl.elements.remove(element.key) - } - } - } - - val alpha = - remember(layoutImpl, element, scene, sceneValues) { - derivedStateOf { elementAlpha(layoutImpl, element, scene, sceneValues) } - } - val isOpaque by remember(alpha) { derivedStateOf { alpha.value == 1f } } - SideEffect { - if (isOpaque) { - lastSharedValues.alpha = 1f - lastSceneValues.alpha = 1f - } +): Modifier { + val element: Element + val sceneValues: Element.TargetValues + + // Get the element associated to [key] if it was already composed in another scene, + // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a + // withoutReadObservation() because there is no need to recompose when that map is mutated. + Snapshot.withoutReadObservation { + element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it } + sceneValues = + element.sceneValues[scene.key] + ?: Element.TargetValues(scene.key).also { element.sceneValues[scene.key] = it } } - val drawScale by - remember(layoutImpl, element, scene, sceneValues) { - derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) } - } - - drawWithContent { + return this.then(ElementModifier(layoutImpl, element, sceneValues)) + .drawWithContent { if (shouldDrawElement(layoutImpl, scene, element)) { + val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues) if (drawScale == Scale.Default) { - this@drawWithContent.drawContent() + drawContent() } else { scale( drawScale.scaleX, drawScale.scaleY, - if (drawScale.pivot.isUnspecified) center else drawScale.pivot + if (drawScale.pivot.isUnspecified) center else drawScale.pivot, ) { this@drawWithContent.drawContent() } @@ -200,15 +169,85 @@ internal fun Modifier.element( place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this) } } - .thenIf(!isOpaque) { - Modifier.graphicsLayer { - val alpha = alpha.value - this.alpha = alpha - lastSharedValues.alpha = alpha - lastSceneValues.alpha = alpha + .testTag(key.testTag) +} + +/** + * An element associated to [ElementNode]. Note that this element does not support updates as its + * arguments should always be the same. + */ +private data class ElementModifier( + private val layoutImpl: SceneTransitionLayoutImpl, + private val element: Element, + private val sceneValues: Element.TargetValues, +) : ModifierNodeElement<ElementNode>() { + override fun create(): ElementNode = ElementNode(layoutImpl, element, sceneValues) + + override fun update(node: ElementNode) { + node.update(layoutImpl, element, sceneValues) + } +} + +internal class ElementNode( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + sceneValues: Element.TargetValues, +) : Modifier.Node() { + private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl + private var element: Element = element + private var sceneValues: Element.TargetValues = sceneValues + + override fun onAttach() { + super.onAttach() + addNodeToSceneValues() + } + + private fun addNodeToSceneValues() { + sceneValues.nodes.add(this) + + coroutineScope.launch { + // At this point all [CodeLocationNode] have been attached or detached, which means that + // [sceneValues.codeLocations] should have exactly 1 element, otherwise this means that + // this element was composed multiple times in the same scene. + val nCodeLocations = sceneValues.nodes.size + if (nCodeLocations != 1 || !sceneValues.nodes.contains(this@ElementNode)) { + error("${element.key} was composed $nCodeLocations times in ${sceneValues.scene}") } } - .testTag(key.testTag) + } + + override fun onDetach() { + super.onDetach() + removeNodeFromSceneValues() + } + + private fun removeNodeFromSceneValues() { + sceneValues.nodes.remove(this) + + // If element is not composed from this scene anymore, remove the scene values. This works + // because [onAttach] is called before [onDetach], so if an element is moved from the UI + // tree we will first add the new code location then remove the old one. + if (sceneValues.nodes.isEmpty()) { + element.sceneValues.remove(sceneValues.scene) + } + + // If the element is not composed in any scene, remove it from the elements map. + if (element.sceneValues.isEmpty()) { + layoutImpl.elements.remove(element.key) + } + } + + fun update( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + sceneValues: Element.TargetValues, + ) { + removeNodeFromSceneValues() + this.layoutImpl = layoutImpl + this.element = element + this.sceneValues = sceneValues + addNodeToSceneValues() + } } private fun shouldDrawElement( @@ -325,6 +364,61 @@ private fun Modifier.modifierTransformations( } } +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [elementAlpha]. Note that we don't + * reuse [elementAlpha] and simply check if alpha == 1f because [isElementOpaque] is checked during + * placement and we don't want to read the transition progress in that phase. + */ +private fun isElementOpaque( + layoutImpl: SceneTransitionLayoutImpl, + element: Element, + scene: Scene, + sceneValues: Element.TargetValues, +): Boolean { + val state = layoutImpl.state.transitionState + + if (state !is TransitionState.Transition || state.fromScene == state.toScene) { + return true + } + + if (!layoutImpl.isTransitionReady(state)) { + val lastValue = + sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified } + ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f + + return lastValue == 1f + } + + val fromScene = state.fromScene + val toScene = state.toScene + val fromValues = element.sceneValues[fromScene] + val toValues = element.sceneValues[toScene] + + if (fromValues == null && toValues == null) { + error("This should not happen, element $element is neither in $fromScene or $toScene") + } + + val isSharedElement = fromValues != null && toValues != null + if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { + return true + } + + return layoutImpl.transitions + .transitionSpec(fromScene, toScene) + .transformations(element.key, scene.key) + .alpha == null +} + +/** + * Whether the element is opaque or not. + * + * Important: The logic here should closely match the logic in [isElementOpaque]. Note that we don't + * reuse [elementAlpha] in [isElementOpaque] and simply check if alpha == 1f because + * [isElementOpaque] is checked during placement and we don't want to read the transition progress + * in that phase. + */ private fun elementAlpha( layoutImpl: SceneTransitionLayoutImpl, element: Element, @@ -446,6 +540,8 @@ private fun IntermediateMeasureScope.place( } val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero) + val lastSharedValues = element.lastSharedValues + val lastValues = sceneValues.lastValues val targetOffset = computeValue( layoutImpl, @@ -456,16 +552,31 @@ private fun IntermediateMeasureScope.place( idleValue = targetOffsetInScene, currentValue = { currentOffset }, lastValue = { - sceneValues.lastValues.offset.takeIf { it.isSpecified } - ?: element.lastSharedValues.offset.takeIf { it.isSpecified } - ?: currentOffset + lastValues.offset.takeIf { it.isSpecified } + ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset }, ::lerp, ) - element.lastSharedValues.offset = targetOffset - sceneValues.lastValues.offset = targetOffset - placeable.place((targetOffset - currentOffset).round()) + lastSharedValues.offset = targetOffset + lastValues.offset = targetOffset + + val offset = (targetOffset - currentOffset).round() + if (isElementOpaque(layoutImpl, element, scene, sceneValues)) { + // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not + // animated once b/305195729 is fixed. Test that drawing is not invalidated in that + // case. + placeable.place(offset) + lastSharedValues.alpha = 1f + lastValues.alpha = 1f + } else { + placeable.placeWithLayer(offset) { + val alpha = elementAlpha(layoutImpl, element, scene, sceneValues) + this.alpha = alpha + lastSharedValues.alpha = alpha + lastValues.alpha = alpha + } + } } } @@ -527,21 +638,17 @@ private inline fun <T> computeValue( error("This should not happen, element $element is neither in $fromScene or $toScene") } - // TODO(b/291053278): Handle overscroll correctly. We should probably coerce between [0f, 1f] - // here and consume overflows at drawing time, somehow reusing Compose OverflowEffect or some - // similar mechanism. - val transitionProgress = state.progress - // The element is shared: interpolate between the value in fromScene and the value in toScene. // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromValues != null && toValues != null if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) { - return lerp( - sceneValue(fromValues!!), - sceneValue(toValues!!), - transitionProgress, - ) + val start = sceneValue(fromValues!!) + val end = sceneValue(toValues!!) + + // Make sure we don't read progress if values are the same and we don't need to interpolate, + // so we don't invalidate the phase where this is read. + return if (start == end) start else lerp(start, end, state.progress) } val transformation = @@ -576,8 +683,15 @@ private inline fun <T> computeValue( idleValue, ) + // Make sure we don't read progress if values are the same and we don't need to interpolate, so + // we don't invalidate the phase where this is read. + if (targetValue == idleValue) { + return targetValue + } + + val progress = state.progress // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range. - val rangeProgress = transformation.range?.progress(transitionProgress) ?: transitionProgress + val rangeProgress = transformation.range?.progress(progress) ?: progress // Interpolate between the value at rest and the value before entering/after leaving. val isEntering = scene.key == toScene diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index bc015eedb1b4..5b752eb4e900 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt @@ -43,7 +43,10 @@ class SceneKey( name: String, identity: Any = Object(), ) : Key(name, identity) { - @VisibleForTesting val testTag: String = "scene:$name" + @VisibleForTesting + // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can + // access internal members. + val testTag: String = "scene:$name" /** The unique [ElementKey] identifying this scene's root element. */ val rootElementKey = ElementKey(name, identity) @@ -64,7 +67,10 @@ class ElementKey( */ val isBackground: Boolean = false, ) : Key(name, identity), ElementMatcher { - @VisibleForTesting val testTag: String = "element:$name" + @VisibleForTesting + // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can + // access internal members. + val testTag: String = "element:$name" override fun matches(key: ElementKey, scene: SceneKey): Boolean { return key == this diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 1a79522da05d..857a596a1404 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -76,6 +76,8 @@ private class SceneScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val scene: Scene, ) : SceneScope { + override val layoutState: SceneTransitionLayoutState = layoutImpl.state + override fun Modifier.element(key: ElementKey): Modifier { return element(layoutImpl, scene, key) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index 9d71801be25b..c51287a7bb71 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import android.util.Log -import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -37,15 +36,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -@VisibleForTesting -class SceneGestureHandler( - private val layoutImpl: SceneTransitionLayoutImpl, +internal class SceneGestureHandler( + internal val layoutImpl: SceneTransitionLayoutImpl, internal val orientation: Orientation, private val coroutineScope: CoroutineScope, ) { val draggable: DraggableHandler = SceneDraggableHandler(this) - private var transitionState + internal var transitionState get() = layoutImpl.state.transitionState set(value) { layoutImpl.state.transitionState = value @@ -58,17 +56,15 @@ class SceneGestureHandler( * Note: the initialScene here does not matter, it's only used for initializing the transition * and will be replaced when a drag event starts. */ - private val swipeTransition = SwipeTransition(initialScene = currentScene) + internal val swipeTransition = SwipeTransition(initialScene = currentScene) internal val currentScene: Scene get() = layoutImpl.scene(transitionState.currentScene) - @VisibleForTesting - val isDrivingTransition + internal val isDrivingTransition get() = transitionState == swipeTransition - @VisibleForTesting - var isAnimatingOffset + internal var isAnimatingOffset get() = swipeTransition.isAnimatingOffset private set(value) { swipeTransition.isAnimatingOffset = value @@ -81,7 +77,7 @@ class SceneGestureHandler( * The velocity threshold at which the intent of the user is to swipe up or down. It is the same * as SwipeableV2Defaults.VelocityThreshold. */ - @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() } + internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() } /** * The positional threshold at which the intent of the user is to swipe to the next scene. It is @@ -415,7 +411,7 @@ class SceneGestureHandler( } } - private class SwipeTransition(initialScene: Scene) : TransitionState.Transition { + internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition { var _currentScene by mutableStateOf(initialScene) override val currentScene: SceneKey get() = _currentScene.key @@ -533,8 +529,7 @@ private class SceneDraggableHandler( } } -@VisibleForTesting -class SceneNestedScrollHandler( +internal class SceneNestedScrollHandler( private val gestureHandler: SceneGestureHandler, private val startBehavior: NestedScrollBehavior, private val endBehavior: NestedScrollBehavior, @@ -598,9 +593,29 @@ class SceneNestedScrollHandler( return PriorityNestedScrollConnection( canStartPreScroll = { offsetAvailable, offsetBeforeStart -> canChangeScene = offsetBeforeStart == Offset.Zero - gestureHandler.isDrivingTransition && + + val canInterceptSwipeTransition = canChangeScene && - offsetAvailable.toAmount() != 0f + gestureHandler.isDrivingTransition && + offsetAvailable.toAmount() != 0f + if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false + + val progress = gestureHandler.swipeTransition.progress + val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold + fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold + + // The transition is always between 0 and 1. If it is close to either of these + // intervals, we want to go directly to the TransitionState.Idle. + // The progress value can go beyond this range in the case of overscroll. + val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f) + if (shouldSnapToIdle) { + gestureHandler.swipeTransition.stopOffsetAnimation() + gestureHandler.transitionState = + TransitionState.Idle(gestureHandler.swipeTransition.currentScene) + } + + // Start only if we cannot consume this event + !shouldSnapToIdle }, canStartPostScroll = { offsetAvailable, offsetBeforeStart -> val amount = offsetAvailable.toAmount() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index efdfe7a7921e..30d13dfa3f70 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import androidx.annotation.FloatRange import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.State @@ -41,6 +42,8 @@ import androidx.compose.ui.platform.LocalDensity * @param transitions the definition of the transitions used to animate a change of scene. * @param state the observable state of this layout. * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any. + * @param transitionInterceptionThreshold used during a scene transition. For the scene to be + * intercepted, the progress value must be above the threshold, and below (1 - threshold). * @param scenes the configuration of the different scenes of this layout. */ @Composable @@ -51,30 +54,20 @@ fun SceneTransitionLayout( modifier: Modifier = Modifier, state: SceneTransitionLayoutState = remember { SceneTransitionLayoutState(currentScene) }, edgeDetector: EdgeDetector = DefaultEdgeDetector, + @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, scenes: SceneTransitionLayoutScope.() -> Unit, ) { - val density = LocalDensity.current - val coroutineScope = rememberCoroutineScope() - val layoutImpl = remember { - SceneTransitionLayoutImpl( - onChangeScene = onChangeScene, - builder = scenes, - transitions = transitions, - state = state, - density = density, - edgeDetector = edgeDetector, - coroutineScope = coroutineScope, - ) - } - - layoutImpl.onChangeScene = onChangeScene - layoutImpl.transitions = transitions - layoutImpl.density = density - layoutImpl.edgeDetector = edgeDetector - - layoutImpl.setScenes(scenes) - layoutImpl.setCurrentScene(currentScene) - layoutImpl.Content(modifier) + SceneTransitionLayoutForTesting( + currentScene, + onChangeScene, + transitions, + state, + edgeDetector, + transitionInterceptionThreshold, + modifier, + onLayoutImpl = null, + scenes, + ) } interface SceneTransitionLayoutScope { @@ -102,6 +95,9 @@ interface SceneTransitionLayoutScope { @ElementDsl interface SceneScope { + /** The state of the [SceneTransitionLayout] in which this scene is contained. */ + val layoutState: SceneTransitionLayoutState + /** * Tag an element identified by [key]. * @@ -222,3 +218,47 @@ enum class SwipeDirection(val orientation: Orientation) { Left(Orientation.Horizontal), Right(Orientation.Horizontal), } + +/** + * An internal version of [SceneTransitionLayout] to be used for tests. + * + * Important: You should use this only in tests and if you need to access the underlying + * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout]. + */ +@Composable +internal fun SceneTransitionLayoutForTesting( + currentScene: SceneKey, + onChangeScene: (SceneKey) -> Unit, + transitions: SceneTransitions, + state: SceneTransitionLayoutState, + edgeDetector: EdgeDetector, + transitionInterceptionThreshold: Float, + modifier: Modifier, + onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?, + scenes: SceneTransitionLayoutScope.() -> Unit, +) { + val density = LocalDensity.current + val coroutineScope = rememberCoroutineScope() + val layoutImpl = remember { + SceneTransitionLayoutImpl( + onChangeScene = onChangeScene, + builder = scenes, + transitions = transitions, + state = state, + density = density, + edgeDetector = edgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, + coroutineScope = coroutineScope, + ) + .also { onLayoutImpl?.invoke(it) } + } + + layoutImpl.onChangeScene = onChangeScene + layoutImpl.transitions = transitions + layoutImpl.density = density + layoutImpl.edgeDetector = edgeDetector + + layoutImpl.setScenes(scenes) + layoutImpl.setCurrentScene(currentScene) + layoutImpl.Content(modifier) +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 0b06953bc8e2..60f385aede02 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -17,7 +17,6 @@ package com.android.compose.animation.scene import androidx.activity.compose.BackHandler -import androidx.annotation.VisibleForTesting import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable @@ -42,14 +41,14 @@ import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel -@VisibleForTesting -class SceneTransitionLayoutImpl( +internal class SceneTransitionLayoutImpl( onChangeScene: (SceneKey) -> Unit, builder: SceneTransitionLayoutScope.() -> Unit, transitions: SceneTransitions, internal val state: SceneTransitionLayoutState, density: Density, edgeDetector: EdgeDetector, + transitionInterceptionThreshold: Float, coroutineScope: CoroutineScope, ) { internal val scenes = SnapshotStateMap<SceneKey, Scene>() @@ -62,6 +61,7 @@ class SceneTransitionLayoutImpl( internal var transitions by mutableStateOf(transitions) internal var density: Density by mutableStateOf(density) internal var edgeDetector by mutableStateOf(edgeDetector) + internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold) private val horizontalGestureHandler: SceneGestureHandler private val verticalGestureHandler: SceneGestureHandler @@ -258,8 +258,7 @@ class SceneTransitionLayoutImpl( internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene) - @VisibleForTesting - fun setScenesTargetSizeForTest(size: IntSize) { + internal fun setScenesTargetSizeForTest(size: IntSize) { scenes.values.forEach { it.targetSize = size } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index b9f83c545122..64c9775a386e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -30,6 +30,22 @@ class SceneTransitionLayoutState(initialScene: SceneKey) { */ var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene)) internal set + + /** + * Whether we are transitioning, optionally restricting the check to the transition between + * [from] and [to]. + */ + fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { + val transition = transitionState as? TransitionState.Transition ?: return false + + // TODO(b/310915136): Remove this check. + if (transition.fromScene == transition.toScene) { + return false + } + + return (from == null || transition.fromScene == from) && + (to == null || transition.toScene == to) + } } sealed interface TransitionState { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 72a2d612d8e3..2172ed300a33 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -16,7 +16,6 @@ package com.android.compose.animation.scene -import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.snap import androidx.compose.ui.geometry.Offset @@ -38,12 +37,11 @@ import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ class SceneTransitions( - @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>, + internal val transitionSpecs: List<TransitionSpec>, ) { private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>() - @VisibleForTesting - fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec { + internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec { return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) } } diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp index 6de75501dd34..13df35b7e1e8 100644 --- a/packages/SystemUI/compose/scene/tests/Android.bp +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -30,10 +30,13 @@ android_test { srcs: [ "src/**/*.kt", + + // TODO(b/240432457): Depend on PlatformComposeSceneTransitionLayout + // directly once Kotlin tests can access internal declarations. + ":PlatformComposeSceneTransitionLayout-srcs", ], static_libs: [ - "PlatformComposeSceneTransitionLayout", "PlatformComposeSceneTransitionLayoutTestsUtils", "androidx.test.runner", diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt new file mode 100644 index 000000000000..cc7a0b8e33b2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.intermediateLayout +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ElementTest { + @get:Rule val rule = createComposeRule() + + @Composable + @OptIn(ExperimentalComposeUiApi::class) + private fun SceneScope.Element( + key: ElementKey, + size: Dp, + offset: Dp, + modifier: Modifier = Modifier, + onLayout: () -> Unit = {}, + onPlacement: () -> Unit = {}, + ) { + Box( + modifier + .offset(offset) + .element(key) + .intermediateLayout { measurable, constraints -> + onLayout() + val placement = measurable.measure(constraints) + layout(placement.width, placement.height) { + onPlacement() + placement.place(0, 0) + } + } + .size(size) + ) + } + + @Test + fun staticElements_noLayout_noPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + val elementOffset = 20.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + elementOffset, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + elementOffset, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, elementOffset) + } + }, + transition = { + spec = tween(nFrames * 16) + + // no-op transformations. + translate(TestElements.Bar, x = 0.dp, y = 0.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var numberOfPlacementsAfterOneAnimationFrame = 0 + + fun assertNumberOfLayoutsAndPlacements() { + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(fooPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barPlacements).isEqualTo(numberOfPlacementsAfterOneAnimationFrame) + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + numberOfPlacementsAfterOneAnimationFrame = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) don't relayout or replace + // static (shared or transformed) elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } + + @Test + fun onlyMovingElements_noLayout_onlyPlacement() { + val nFrames = 20 + val layoutSize = 100.dp + val elementSize = 50.dp + + var fooLayouts = 0 + var fooPlacements = 0 + var barLayouts = 0 + var barPlacements = 0 + + rule.testTransition( + fromSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element( + TestElements.Foo, + elementSize, + offset = 0.dp, + onLayout = { fooLayouts++ }, + onPlacement = { fooPlacements++ }, + ) + + // Transformed element + Element( + TestElements.Bar, + elementSize, + offset = 0.dp, + onLayout = { barLayouts++ }, + onPlacement = { barPlacements++ }, + ) + } + }, + toSceneContent = { + Box(Modifier.size(layoutSize)) { + // Shared element. + Element(TestElements.Foo, elementSize, offset = 20.dp) + } + }, + transition = { + spec = tween(nFrames * 16) + + // Only translate Bar. + translate(TestElements.Bar, x = 20.dp, y = 20.dp) + scaleSize(TestElements.Bar, width = 1f, height = 1f) + }, + ) { + var numberOfLayoutsAfterOneAnimationFrame = 0 + var lastNumberOfPlacements = 0 + + fun assertNumberOfLayoutsAndPlacements() { + // The number of layouts have not changed. + assertThat(fooLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + assertThat(barLayouts).isEqualTo(numberOfLayoutsAfterOneAnimationFrame) + + // The number of placements have increased. + assertThat(fooPlacements).isGreaterThan(lastNumberOfPlacements) + assertThat(barPlacements).isGreaterThan(lastNumberOfPlacements) + lastNumberOfPlacements = fooPlacements + } + + at(16) { + // Capture the number of layouts and placements that happened after 1 animation + // frame. + numberOfLayoutsAfterOneAnimationFrame = fooLayouts + lastNumberOfPlacements = fooPlacements + } + repeat(nFrames - 2) { i -> + // Ensure that all animation frames (except the final one) only replaced the + // elements. + at(32L + i * 16) { assertNumberOfLayoutsAndPlacements() } + } + } + } + + @Test + fun elementIsReusedInSameSceneAndBetweenScenes() { + var currentScene by mutableStateOf(TestScenes.SceneA) + var sceneCState by mutableStateOf(0) + var sceneDState by mutableStateOf(0) + val key = TestElements.Foo + var nullableLayoutImpl: SceneTransitionLayoutImpl? = null + + rule.setContent { + SceneTransitionLayoutForTesting( + currentScene = currentScene, + onChangeScene = { currentScene = it }, + transitions = remember { transitions {} }, + state = remember { SceneTransitionLayoutState(currentScene) }, + edgeDetector = DefaultEdgeDetector, + modifier = Modifier, + transitionInterceptionThreshold = 0f, + onLayoutImpl = { nullableLayoutImpl = it }, + ) { + scene(TestScenes.SceneA) { /* Nothing */} + scene(TestScenes.SceneB) { Box(Modifier.element(key)) } + scene(TestScenes.SceneC) { + when (sceneCState) { + 0 -> Row(Modifier.element(key)) {} + 1 -> Column(Modifier.element(key)) {} + else -> { + /* Nothing */ + } + } + } + scene(TestScenes.SceneD) { + // We should be able to extract the modifier before assigning it to different + // nodes. + val childModifier = Modifier.element(key) + when (sceneDState) { + 0 -> Row(childModifier) {} + 1 -> Column(childModifier) {} + else -> { + /* Nothing */ + } + } + } + } + } + + assertThat(nullableLayoutImpl).isNotNull() + val layoutImpl = nullableLayoutImpl!! + + // Scene A: no elements in the elements map. + rule.waitForIdle() + assertThat(layoutImpl.elements).isEmpty() + + // Scene B: element is in the map. + currentScene = TestScenes.SceneB + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(key) + val element = layoutImpl.elements.getValue(key) + assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneB) + + // Scene C, state 0: the same element is reused. + currentScene = TestScenes.SceneC + sceneCState = 0 + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(key) + assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element) + assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC) + + // Scene C, state 1: the same element is reused. + sceneCState = 1 + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(key) + assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element) + assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC) + + // Scene D, state 0: the same element is reused. + currentScene = TestScenes.SceneD + sceneDState = 0 + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(key) + assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element) + assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD) + + // Scene D, state 1: the same element is reused. + sceneDState = 1 + rule.waitForIdle() + + assertThat(layoutImpl.elements.keys).containsExactly(key) + assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element) + assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD) + + // Scene D, state 2: the element is removed from the map. + sceneDState = 2 + rule.waitForIdle() + + assertThat(element.sceneValues).isEmpty() + assertThat(layoutImpl.elements).isEmpty() + } + + @Test + fun throwsExceptionWhenElementIsComposedMultipleTimes() { + val key = TestElements.Foo + + assertThrows(IllegalStateException::class.java) { + rule.setContent { + TestSceneScope { + Column { + Box(Modifier.element(key)) + Box(Modifier.element(key)) + } + } + } + } + } + + @Test + fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier() { + val key = TestElements.Foo + + assertThrows(IllegalStateException::class.java) { + rule.setContent { + TestSceneScope { + Column { + val childModifier = Modifier.element(key) + Box(childModifier) + Box(childModifier) + } + } + } + } + } + + @Test + fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier_laterDuplication() { + val key = TestElements.Foo + + assertThrows(IllegalStateException::class.java) { + var nElements by mutableStateOf(1) + rule.setContent { + TestSceneScope { + Column { + val childModifier = Modifier.element(key) + repeat(nElements) { Box(childModifier) } + } + } + } + + nElements = 2 + rule.waitForIdle() + } + } + + @Test + fun throwsExceptionWhenElementIsComposedMultipleTimes_updatedNode() { + assertThrows(IllegalStateException::class.java) { + var key by mutableStateOf(TestElements.Foo) + rule.setContent { + TestSceneScope { + Column { + Box(Modifier.element(key)) + Box(Modifier.element(TestElements.Bar)) + } + } + } + + key = TestElements.Bar + rule.waitForIdle() + } + } + + @Test + fun elementModifierSupportsUpdates() { + var key by mutableStateOf(TestElements.Foo) + var nullableLayoutImpl: SceneTransitionLayoutImpl? = null + + rule.setContent { + SceneTransitionLayoutForTesting( + currentScene = TestScenes.SceneA, + onChangeScene = {}, + transitions = remember { transitions {} }, + state = remember { SceneTransitionLayoutState(TestScenes.SceneA) }, + edgeDetector = DefaultEdgeDetector, + modifier = Modifier, + transitionInterceptionThreshold = 0f, + onLayoutImpl = { nullableLayoutImpl = it }, + ) { + scene(TestScenes.SceneA) { Box(Modifier.element(key)) } + } + } + + assertThat(nullableLayoutImpl).isNotNull() + val layoutImpl = nullableLayoutImpl!! + + // There is only Foo in the elements map. + assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo) + val fooElement = layoutImpl.elements.getValue(TestElements.Foo) + assertThat(fooElement.sceneValues.keys).containsExactly(TestScenes.SceneA) + + key = TestElements.Bar + + // There is only Bar in the elements map and foo scene values was cleaned up. + rule.waitForIdle() + assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Bar) + val barElement = layoutImpl.elements.getValue(TestElements.Bar) + assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA) + assertThat(fooElement.sceneValues).isEmpty() + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 7ab2096b3d88..49ef31b16d73 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -69,6 +69,8 @@ class SceneGestureHandlerTest { scene(SceneC) { Text("SceneC") } } + val transitionInterceptionThreshold = 0.05f + val sceneGestureHandler = SceneGestureHandler( layoutImpl = @@ -79,6 +81,7 @@ class SceneGestureHandlerTest { state = layoutState, density = Density(1f), edgeDetector = DefaultEdgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, coroutineScope = coroutineScope, ) .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }, @@ -107,6 +110,9 @@ class SceneGestureHandlerTest { val transitionState: TransitionState get() = layoutState.transitionState + val progress: Float + get() = (transitionState as Transition).progress + fun advanceUntilIdle() { coroutineScope.testScheduler.advanceUntilIdle() } @@ -145,13 +151,12 @@ class SceneGestureHandlerTest { fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) draggable.onDelta(pixels = deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) } @Test @@ -257,8 +262,7 @@ class SceneGestureHandlerTest { ) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) assertThat(consumed).isEqualTo(offsetY10) } @@ -282,13 +286,12 @@ class SceneGestureHandlerTest { nestedScroll.scroll(available = offsetY10) assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // start intercept preScroll val consumed = nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // do nothing on postScroll nestedScroll.onPostScroll( @@ -296,13 +299,71 @@ class SceneGestureHandlerTest { available = Offset.Zero, source = NestedScrollSource.Drag ) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) assertScene(currentScene = SceneA, isIdle = false) } + private suspend fun TestGestureScope.preScrollAfterSceneTransition( + firstScroll: Float, + secondScroll: Float + ) { + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + // start scene transition + nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll)) + + // stop scene transition (start the "stop animation") + nestedScroll.onPreFling(available = Velocity.Zero) + + // a pre scroll event, that could be intercepted by SceneGestureHandler + nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag) + } + + // Float tolerance for comparisons + private val tolerance = 0.00001f + + @Test + fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest { + val first = transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneA, isIdle = true) + } + + @Test + fun scrollAndFling_scrollMinInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMaxInterceptable_interceptPreScrollEvents() = runGestureTest { + val first = 1f - transitionInterceptionThreshold - tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertThat(progress).isWithin(tolerance).of(first + second) + } + + @Test + fun scrollAndFling_scrollMoreThanInterceptable_goToIdleOnNextScene() = runGestureTest { + val first = 1f - transitionInterceptionThreshold + tolerance + val second = 0.01f + + preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) + + assertScene(SceneC, isIdle = true) + } + @Test fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) @@ -444,24 +505,23 @@ class SceneGestureHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always) draggable.onDragStarted() assertScene(currentScene = SceneA, isIdle = false) - val transition = transitionState as Transition draggable.onDelta(deltaInPixels10) - assertThat(transition.progress).isEqualTo(0.1f) + assertThat(progress).isEqualTo(0.1f) // now we can intercept the scroll events nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.2f) + assertThat(progress).isEqualTo(0.2f) // this should be ignored, we are scrolling now! draggable.onDragStopped(velocityThreshold) assertScene(currentScene = SceneA, isIdle = false) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.3f) + assertThat(progress).isEqualTo(0.3f) nestedScroll.scroll(available = offsetY10) - assertThat(transition.progress).isEqualTo(0.4f) + assertThat(progress).isEqualTo(0.4f) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) assertScene(currentScene = SceneC, isIdle = false) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt new file mode 100644 index 000000000000..94c51ca50667 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SceneTransitionLayoutStateTest { + @get:Rule val rule = createComposeRule() + + @Test + fun isTransitioningTo_idle() { + val state = SceneTransitionLayoutState(TestScenes.SceneA) + + assertThat(state.isTransitioning()).isFalse() + assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse() + assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse() + assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)) + .isFalse() + } + + @Test + fun isTransitioningTo_fromSceneEqualToToScene() { + val state = SceneTransitionLayoutState(TestScenes.SceneA) + state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA) + + assertThat(state.isTransitioning()).isFalse() + assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse() + assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse() + assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)) + .isFalse() + } + + @Test + fun isTransitioningTo_transition() { + val state = SceneTransitionLayoutState(TestScenes.SceneA) + state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB) + + assertThat(state.isTransitioning()).isTrue() + assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue() + assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse() + assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue() + assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse() + assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() + } + + private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition { + return object : TransitionState.Transition { + override val currentScene: SceneKey = from + override val fromScene: SceneKey = from + override val toScene: SceneKey = to + override val progress: Float = 0f + override val isInitiatedByUserInput: Boolean = false + override val isUserInputOngoing: Boolean = false + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt index b4c393e9bfbe..b83705aa64fc 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt @@ -26,6 +26,7 @@ object TestScenes { val SceneA = SceneKey("SceneA") val SceneB = SceneKey("SceneB") val SceneC = SceneKey("SceneC") + val SceneD = SceneKey("SceneD") } /** Element keys that can be reused by tests. */ diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py index 5db27d8a9529..bac3553e7498 100755 --- a/packages/SystemUI/flag_check.py +++ b/packages/SystemUI/flag_check.py @@ -14,7 +14,7 @@ following case-sensitive regex: The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the -flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD). +flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD). Some examples below: @@ -74,11 +74,11 @@ def main(): #common_typos_disable flagName = '([a-zA-z0-9_.])+' - #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] - stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' + #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] + stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' #common_typos_enable - readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD' + readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD' flagRegex = fr'^{field}: .*$' check_flag = re.compile(flagRegex) #Flag: diff --git a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml index be8fe8c57215..ff1146e9b945 100644 --- a/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml +++ b/packages/SystemUI/res/drawable/ic_ksh_key_meta.xml @@ -19,9 +19,14 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - <path android:fillColor="@color/ksh_key_item_color" - android:pathData="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 -3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27 .28 v.79l5 4.99L20.49 -19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 -14z" /> + <path android:pathData="M5.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M5.5,16.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M16.5,5.5m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" + android:fillColor="@color/ksh_key_item_color" /> + <path android:pathData="M18.5,16.5C18.5,15.4 17.6,14.5 16.5,14.5C15.4,14.5 14.5,15.4 14.5,16.5C14.5,17.6 15.4,18.5 16.5,18.5C17.6,18.5 18.5,17.6 18.5,16.5ZM12.5,16.5C12.5,14.29 14.29,12.5 16.5,12.5C18.71,12.5 20.5,14.29 20.5,16.5C20.5,17.241 20.299,17.934 19.948,18.529L23,21.59L21.59,23L18.529,19.948C17.934,20.299 17.241,20.5 16.5,20.5C14.29,20.5 12.5,18.71 12.5,16.5Z" + android:fillColor="@color/ksh_key_item_color" + android:fillType="evenOdd" /> </vector> + diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml index 2459eeaa9b19..cb638ee87834 100644 --- a/packages/SystemUI/res/layout/app_clips_screenshot.xml +++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml @@ -28,26 +28,28 @@ style="@android:style/Widget.DeviceDefault.Button.Colored" android:layout_width="wrap_content" android:layout_height="48dp" - android:text="@string/app_clips_save_add_to_note" android:layout_marginStart="8dp" android:background="@drawable/overlay_button_background" + android:paddingHorizontal="24dp" + android:text="@string/app_clips_save_add_to_note" android:textColor="?android:textColorSecondary" + app:layout_constraintBottom_toTopOf="@id/preview" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/preview" /> + app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/cancel" style="@android:style/Widget.DeviceDefault.Button.Colored" android:layout_width="wrap_content" android:layout_height="48dp" - android:text="@android:string/cancel" - android:layout_marginStart="6dp" + android:layout_marginStart="8dp" android:background="@drawable/overlay_button_background" + android:paddingHorizontal="24dp" + android:text="@android:string/cancel" android:textColor="?android:textColorSecondary" + app:layout_constraintBottom_toTopOf="@id/preview" app:layout_constraintStart_toEndOf="@id/save" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintBottom_toTopOf="@id/preview" /> + app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/preview" diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 08eccbbe9669..4336ccc70c33 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -21,7 +21,7 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" android:layout_height="@dimen/bluetooth_dialog_device_height" - android:paddingEnd="24dp" + android:paddingEnd="0dp" android:paddingStart="20dp" android:layout_marginBottom="4dp"> @@ -86,6 +86,7 @@ android:id="@+id/gear_icon_image" android:layout_width="0dp" android:layout_height="24dp" + android:paddingEnd="24dp" android:src="@drawable/ic_settings_24dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index c11a18b795a1..af29cada2657 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -120,6 +120,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_arrow,see_all_text" /> + <View + android:id="@+id/see_all_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/device_list" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" /> + <ImageView android:id="@+id/ic_arrow" android:layout_marginStart="36dp" @@ -141,6 +151,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/see_all_bluetooth_devices" @@ -158,6 +170,16 @@ android:visibility="gone" app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + <View + android:id="@+id/pair_new_device_clickable_row" + android:layout_width="0dp" + android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@+id/see_all_text" + app:layout_constraintBottom_toTopOf="@+id/done_button" /> + <ImageView android:id="@+id/ic_add" android:layout_width="24dp" @@ -180,6 +202,8 @@ android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical" + android:importantForAccessibility="no" + android:clickable="false" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/pair_new_bluetooth_devices" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9a3c6d5b322f..3163533a3f4d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -467,6 +467,10 @@ <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] --> <string name="accessibility_bluetooth_device_settings_gear">Click to configure device detail</string> + <!-- Content description of the bluetooth device settings see all. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_see_all">Click to see all devices</string> + <!-- Content description of the bluetooth device settings pair new device. [CHAR LIMIT=NONE] --> + <string name="accessibility_bluetooth_device_settings_pair_new_device">Click to pair new device</string> <!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_battery_unknown">Battery percentage unknown.</string> @@ -640,6 +644,10 @@ <string name="quick_settings_bluetooth_device_connected">Connected</string> <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]--> <string name="quick_settings_bluetooth_device_saved">Saved</string> + <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect">disconnect</string> + <!-- QuickSettings: Accessibility label to activate a device [CHAR LIMIT=NONE]--> + <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate">activate</string> <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]--> <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt index 6082fb93aa26..8ad32b4f1695 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt @@ -25,7 +25,11 @@ enum class FingerprintSensorType { UDFPS_ULTRASONIC, UDFPS_OPTICAL, POWER_BUTTON, - HOME_BUTTON + HOME_BUTTON; + + fun isUdfps(): Boolean { + return (this == UDFPS_OPTICAL) || (this == UDFPS_ULTRASONIC) + } } /** Convert [this] to corresponding [FingerprintSensorType] */ diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 6b390b1191d2..c02ffa788d48 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -39,10 +39,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.DisplaySpecific import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer @@ -298,7 +298,7 @@ constructor( object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { if (!isKeyguardVisible) { clock?.run { smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -345,7 +345,7 @@ constructor( parent.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { listenForDozing(this) - if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 32f9c3057753..dedac5596817 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -46,6 +46,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder; import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager; import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel; @@ -62,6 +63,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.AnimationProperties; @@ -105,6 +107,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final DozeParameters mDozeParameters; private final ScreenOffAnimationController mScreenOffAnimationController; private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @@ -179,6 +182,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS LockscreenSmartspaceController smartspaceController, ConfigurationController configurationController, ScreenOffAnimationController screenOffAnimationController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main DelayableExecutor uiExecutor, @@ -202,6 +206,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mSmartspaceController = smartspaceController; mConfigurationController = configurationController; mScreenOffAnimationController = screenOffAnimationController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; @@ -362,7 +367,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } int getNotificationIconAreaHeight() { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return 0; } else if (NotificationIconContainerRefactor.isEnabled()) { return mAodIconContainer != null ? mAodIconContainer.getHeight() : 0; @@ -590,7 +595,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void updateAodIcons() { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { NotificationIconContainer nic = (NotificationIconContainer) mView.findViewById( com.android.systemui.res.R.id.left_aligned_notification_icon_container); @@ -599,12 +604,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsBindHandle.dispose(); } if (nic != null) { - nic.setOnLockScreen(true); final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind( nic, mAodIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mAodIconViewStore); final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility( nic, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 758d1fef6e2e..87d937bc45fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -51,10 +51,9 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.ClockController; @@ -100,7 +99,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; - private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; @@ -136,7 +134,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, KeyguardLogger logger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, @@ -149,9 +146,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mConfigurationController = configurationController; mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, - featureFlags, logger.getBuffer()); + logger.getBuffer()); mInteractionJankMonitor = interactionJankMonitor; - mFeatureFlags = featureFlags; mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; @@ -188,7 +184,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mDumpManager.registerDumpable(getInstanceName(), this); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { startCoroutines(EmptyCoroutineContext.INSTANCE); } } @@ -454,7 +450,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(layout); int guideline; - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { guideline = R.id.split_shade_guideline; } else { guideline = R.id.qs_edge_guideline; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index d524e4a8c408..ef6514447561 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,8 +23,7 @@ import android.util.Property; import android.view.View; import com.android.app.animation.Interpolators; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; @@ -55,7 +54,6 @@ public class KeyguardVisibilityHelper { private boolean mKeyguardViewVisibilityAnimating; private boolean mLastOccludedState = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); - private final FeatureFlags mFeatureFlags; private final LogBuffer mLogBuffer; public KeyguardVisibilityHelper(View view, @@ -63,14 +61,12 @@ public class KeyguardVisibilityHelper { DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, boolean animateYPos, - FeatureFlags featureFlags, LogBuffer logBuffer) { mView = view; mKeyguardStateController = keyguardStateController; mDozeParameters = dozeParameters; mScreenOffAnimationController = screenOffAnimationController; mAnimateYPos = animateYPos; - mFeatureFlags = featureFlags; mLogBuffer = logBuffer; } @@ -167,7 +163,7 @@ public class KeyguardVisibilityHelper { animProps, true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { log("Using GoneToAodTransition"); mKeyguardViewVisibilityAnimating = false; } else { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 59b85d112753..b704f3c89330 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -59,8 +59,8 @@ import javax.inject.Inject; * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton -public class WindowMagnification implements CoreStartable, CommandQueue.Callbacks { - private static final String TAG = "WindowMagnification"; +public class Magnification implements CoreStartable, CommandQueue.Callbacks { + private static final String TAG = "Magnification"; private final ModeSwitchesController mModeSwitchesController; private final Context mContext; @@ -154,7 +154,7 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier; @Inject - public WindowMagnification(Context context, @Main Handler mainHandler, + public Magnification(Context context, @Main Handler mainHandler, CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, SysUiState sysUiState, OverviewProxyService overviewProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, @@ -366,49 +366,53 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback @VisibleForTesting final MagnificationSettingsController.Callback mMagnificationSettingsControllerCallback = new MagnificationSettingsController.Callback() { - @Override - public void onSetMagnifierSize(int displayId, int index) { - mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index)); - mA11yLogger.logWithPosition( - MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED, - index - ); - } + @Override + public void onSetMagnifierSize(int displayId, int index) { + mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index)); + mA11yLogger.logWithPosition( + MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED, + index + ); + } - @Override - public void onSetDiagonalScrolling(int displayId, boolean enable) { - mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable)); - } + @Override + public void onSetDiagonalScrolling(int displayId, boolean enable) { + mHandler.post(() -> onSetDiagonalScrollingInternal(displayId, enable)); + } - @Override - public void onEditMagnifierSizeMode(int displayId, boolean enable) { - mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable)); - mA11yLogger.log(enable - ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED - : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED); - } + @Override + public void onEditMagnifierSizeMode(int displayId, boolean enable) { + mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable)); + mA11yLogger.log(enable + ? + MagnificationSettingsEvent + .MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED + : MagnificationSettingsEvent + .MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED); + } - @Override - public void onMagnifierScale(int displayId, float scale, boolean updatePersistence) { - if (mWindowMagnificationConnectionImpl != null) { - mWindowMagnificationConnectionImpl.onPerformScaleAction( - displayId, scale, updatePersistence); - } - mA11yLogger.logThrottled( - MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED - ); - } + @Override + public void onMagnifierScale(int displayId, float scale, + boolean updatePersistence) { + if (mWindowMagnificationConnectionImpl != null) { + mWindowMagnificationConnectionImpl.onPerformScaleAction( + displayId, scale, updatePersistence); + } + mA11yLogger.logThrottled( + MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED + ); + } - @Override - public void onModeSwitch(int displayId, int newMode) { - mHandler.post(() -> onModeSwitchInternal(displayId, newMode)); - } + @Override + public void onModeSwitch(int displayId, int newMode) { + mHandler.post(() -> onModeSwitchInternal(displayId, newMode)); + } - @Override - public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) { - mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown)); - } - }; + @Override + public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) { + mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown)); + } + }; @MainThread private void onSetMagnifierSizeInternal(int displayId, int index) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java index ee7781d8af20..b4530ace68d6 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java @@ -34,7 +34,7 @@ import com.android.systemui.util.settings.SecureSettings; * A class to control {@link WindowMagnificationSettings} and receive settings panel callbacks by * {@link WindowMagnificationSettingsCallback}. * The settings panel callbacks will be delegated through - * {@link MagnificationSettingsController.Callback} to {@link WindowMagnification}. + * {@link MagnificationSettingsController.Callback} to {@link Magnification}. */ public class MagnificationSettingsController implements ComponentCallbacks { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index 928445bde8ff..5666851f560f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -37,12 +37,12 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S private static final String TAG = "WindowMagnificationConnectionImpl"; private IWindowMagnificationConnectionCallback mConnectionCallback; - private final WindowMagnification mWindowMagnification; + private final Magnification mMagnification; private final Handler mHandler; - WindowMagnificationConnectionImpl(@NonNull WindowMagnification windowMagnification, + WindowMagnificationConnectionImpl(@NonNull Magnification magnification, @Main Handler mainHandler) { - mWindowMagnification = windowMagnification; + mMagnification = magnification; mHandler = mainHandler; } @@ -51,56 +51,56 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, IRemoteMagnificationAnimationCallback callback) { mHandler.post( - () -> mWindowMagnification.enableWindowMagnification(displayId, scale, centerX, + () -> mMagnification.enableWindowMagnification(displayId, scale, centerX, centerY, magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, callback)); } @Override public void setScale(int displayId, float scale) { - mHandler.post(() -> mWindowMagnification.setScale(displayId, scale)); + mHandler.post(() -> mMagnification.setScale(displayId, scale)); } @Override public void disableWindowMagnification(int displayId, IRemoteMagnificationAnimationCallback callback) { - mHandler.post(() -> mWindowMagnification.disableWindowMagnification(displayId, + mHandler.post(() -> mMagnification.disableWindowMagnification(displayId, callback)); } @Override public void moveWindowMagnifier(int displayId, float offsetX, float offsetY) { mHandler.post( - () -> mWindowMagnification.moveWindowMagnifier(displayId, offsetX, offsetY)); + () -> mMagnification.moveWindowMagnifier(displayId, offsetX, offsetY)); } @Override public void moveWindowMagnifierToPosition(int displayId, float positionX, float positionY, IRemoteMagnificationAnimationCallback callback) { - mHandler.post(() -> mWindowMagnification.moveWindowMagnifierToPositionInternal( + mHandler.post(() -> mMagnification.moveWindowMagnifierToPositionInternal( displayId, positionX, positionY, callback)); } @Override public void showMagnificationButton(int displayId, int magnificationMode) { mHandler.post( - () -> mWindowMagnification.showMagnificationButton(displayId, magnificationMode)); + () -> mMagnification.showMagnificationButton(displayId, magnificationMode)); } @Override public void removeMagnificationButton(int displayId) { mHandler.post( - () -> mWindowMagnification.removeMagnificationButton(displayId)); + () -> mMagnification.removeMagnificationButton(displayId)); } @Override public void removeMagnificationSettingsPanel(int display) { - mHandler.post(() -> mWindowMagnification.hideMagnificationSettingsPanel(display)); + mHandler.post(() -> mMagnification.hideMagnificationSettingsPanel(display)); } @Override public void onUserMagnificationScaleChanged(int userId, int displayId, float scale) { - mHandler.post(() -> mWindowMagnification.setUserMagnificationScale( + mHandler.post(() -> mMagnification.setUserMagnificationScale( userId, displayId, scale)); } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index ee3a55f51679..a42c0ae39c88 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -32,6 +32,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.time.SystemClock @@ -108,6 +109,12 @@ interface AuthenticationRepository { /** The minimal length of a pattern. */ val minPatternLength: Int + /** The minimal length of a password. */ + val minPasswordLength: Int + + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> + /** * Returns the currently-configured authentication method. This determines how the * authentication challenge needs to be completed in order to unlock an otherwise locked device. @@ -165,6 +172,7 @@ constructor( private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, broadcastDispatcher: BroadcastDispatcher, + mobileConnectionsRepository: MobileConnectionsRepository, ) : AuthenticationRepository { override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = @@ -189,9 +197,11 @@ constructor( get() = getSelectedUserInfo().id override val authenticationMethod: Flow<AuthenticationMethodModel> = - userRepository.selectedUserInfo - .map { it.id } - .distinctUntilChanged() + combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) { + selectedUserInfo, + _ -> + selectedUserInfo.id + } .flatMapLatest { selectedUserId -> broadcastDispatcher .broadcastFlow( @@ -209,9 +219,18 @@ constructor( blockingAuthenticationMethodInternal(selectedUserId) } } + .distinctUntilChanged() override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE + override val minPasswordLength: Int = LockPatternUtils.MIN_LOCK_PASSWORD_SIZE + + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = true, + getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, + ) + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { blockingAuthenticationMethodInternal(userRepository.selectedUserId) @@ -345,9 +364,9 @@ constructor( userId: Int, ): AuthenticationMethodModel { return when (getSecurityMode.apply(userId)) { - KeyguardSecurityModel.SecurityMode.PIN, + KeyguardSecurityModel.SecurityMode.PIN -> AuthenticationMethodModel.Pin KeyguardSecurityModel.SecurityMode.SimPin, - KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin + KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Sim KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 1ede5301e751..c2974862bffb 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -145,6 +145,9 @@ constructor( val authenticationChallengeResult: SharedFlow<Boolean> = repository.authenticationChallengeResult + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled + private var throttlingCountdownJob: Job? = null init { @@ -197,9 +200,8 @@ constructor( // We're being throttled, the UI layer should not have called this; skip the // attempt. isThrottled.value -> true - // The pattern is too short; skip the attempt. - authMethod == AuthenticationMethodModel.Pattern && - input.size < repository.minPatternLength -> true + // The input is too short; skip the attempt. + input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. tryAutoConfirm && !isAutoConfirmEnabled.value -> true // Auto-confirm should skip the attempt if the pin entered is too short. @@ -244,6 +246,14 @@ constructor( } } + private fun List<Any>.isTooShort(authMethod: AuthenticationMethodModel): Boolean { + return when (authMethod) { + AuthenticationMethodModel.Pattern -> size < repository.minPatternLength + AuthenticationMethodModel.Password -> size < repository.minPasswordLength + else -> false + } + } + /** Starts refreshing the throttling state every second. */ private suspend fun startThrottlingCountdown() { cancelThrottlingCountdown() diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index bb5b81d4d2f7..3552a1957f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -37,4 +37,6 @@ sealed class AuthenticationMethodModel( object Password : AuthenticationMethodModel(isSecure = true) object Pattern : AuthenticationMethodModel(isSecure = true) + + object Sim : AuthenticationMethodModel(isSecure = true) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 417593787ee9..b064391f74b5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -83,6 +83,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; @@ -170,6 +171,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final SelectedUserInteractor mSelectedUserInteractor; @NonNull private final FpsUnlockTracker mFpsUnlockTracker; private final boolean mIgnoreRefreshRate; + private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @@ -283,8 +285,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, - mUdfpsKeyguardViewModels, - mSelectedUserInteractor + mKeyguardTransitionInteractor, + mSelectedUserInteractor ))); } @@ -649,7 +651,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider, @NonNull SelectedUserInteractor selectedUserInteractor, - @NonNull FpsUnlockTracker fpsUnlockTracker) { + @NonNull FpsUnlockTracker fpsUnlockTracker, + @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -695,6 +698,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mSelectedUserInteractor = selectedUserInteractor; mFpsUnlockTracker = fpsUnlockTracker; mFpsUnlockTracker.startTracking(); + mKeyguardTransitionInteractor = keyguardTransitionInteractor; mTouchProcessor = singlePointerTouchProcessor; mSessionTracker = sessionTracker; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 934f9f919d5d..8f31a2daad37 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -51,8 +51,8 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -63,7 +63,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi -import javax.inject.Provider private const val TAG = "UdfpsControllerOverlay" @@ -102,7 +101,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, + private val transitionInteractor: KeyguardTransitionInteractor, private val selectedUserInteractor: SelectedUserInteractor, ) { /** The view, when [isShowing], or null. */ @@ -264,11 +263,11 @@ class UdfpsControllerOverlay @JvmOverloads constructor( dialogManager, controller, activityLaunchAnimator, - featureFlags, primaryBouncerInteractor, alternateBouncerInteractor, udfpsKeyguardAccessibilityDelegate, selectedUserInteractor, + transitionInteractor, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index d7df0e585dcf..2c4ed5841f1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -16,7 +16,6 @@ package com.android.systemui.biometrics -import android.animation.ValueAnimator import android.content.res.Configuration import android.util.MathUtils import android.view.View @@ -27,17 +26,17 @@ import com.android.app.animation.Interpolators import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI @@ -48,10 +47,14 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.domain.interactor.SelectedUserInteractor import java.io.PrintWriter import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch /** Class that coordinates non-HBM animations during keyguard authentication. */ +@ExperimentalCoroutinesApi open class UdfpsKeyguardViewControllerLegacy( private val view: UdfpsKeyguardViewLegacy, statusBarStateController: StatusBarStateController, @@ -65,11 +68,11 @@ open class UdfpsKeyguardViewControllerLegacy( systemUIDialogManager: SystemUIDialogManager, private val udfpsController: UdfpsController, private val activityLaunchAnimator: ActivityLaunchAnimator, - featureFlags: FeatureFlags, primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, private val selectedUserInteractor: SelectedUserInteractor, + private val transitionInteractor: KeyguardTransitionInteractor, ) : UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>( view, @@ -91,44 +94,10 @@ open class UdfpsKeyguardViewControllerLegacy( private var launchTransitionFadingAway = false private var isLaunchingActivity = false private var activityLaunchProgress = 0f - private val unlockedScreenOffDozeAnimator = - ValueAnimator.ofFloat(0f, 1f).apply { - duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong() - interpolator = Interpolators.ALPHA_IN - addUpdateListener { animation -> - view.onDozeAmountChanged( - animation.animatedFraction, - animation.animatedValue as Float, - UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF - ) - } - } private var inputBouncerExpansion = 0f private val stateListener: StatusBarStateController.StateListener = object : StatusBarStateController.StateListener { - override fun onDozeAmountChanged(linear: Float, eased: Float) { - if (lastDozeAmount < linear) { - showUdfpsBouncer(false) - } - unlockedScreenOffDozeAnimator.cancel() - val animatingFromUnlockedScreenOff = - unlockedScreenOffAnimationController.isAnimationPlaying() - if (animatingFromUnlockedScreenOff && linear != 0f) { - // we manually animate the fade in of the UDFPS icon since the unlocked - // screen off animation prevents the doze amounts to be incrementally eased in - unlockedScreenOffDozeAnimator.start() - } else { - view.onDozeAmountChanged( - linear, - eased, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN - ) - } - lastDozeAmount = linear - updatePauseAuth() - } - override fun onStateChanged(statusBarState: Int) { this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState updateAlpha() @@ -222,11 +191,39 @@ open class UdfpsKeyguardViewControllerLegacy( repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) listenForAlternateBouncerVisibility(this) + listenForGoneToAodTransition(this) + listenForLockscreenAodTransitions(this) + } + } + } + + @VisibleForTesting + suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor.goneToAodTransition.collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATION_UNLOCKED_SCREEN_OFF, + ) } } } @VisibleForTesting + suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor.dozeAmountTransition.collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } + } + } + + @VisibleForTesting override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { return scope.launch { primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java index 95e3a76c11d8..f4ed8cef7bb8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java @@ -73,9 +73,6 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { // AOD anti-burn-in offsets private final int mMaxBurnInOffsetX; private final int mMaxBurnInOffsetY; - private float mBurnInOffsetX; - private float mBurnInOffsetY; - private float mBurnInProgress; private float mInterpolatedDarkAmount; private int mAnimationType = ANIMATION_NONE; private boolean mFullyInflated; @@ -138,20 +135,22 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { // AoD-burn in location, else we need to translate the icon from LS => AoD. final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF ? 1f : mInterpolatedDarkAmount; - mBurnInOffsetX = MathUtils.lerp(0f, + final float burnInOffsetX = MathUtils.lerp(0f, getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - mMaxBurnInOffsetX, darkAmountForAnimation); - mBurnInOffsetY = MathUtils.lerp(0f, + final float burnInOffsetY = MathUtils.lerp(0f, getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - mMaxBurnInOffsetY, darkAmountForAnimation); - mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation); + final float burnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), + darkAmountForAnimation); if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) { - mLockScreenFp.setTranslationX(mBurnInOffsetX); - mLockScreenFp.setTranslationY(mBurnInOffsetY); + mLockScreenFp.setTranslationX(burnInOffsetX); + mLockScreenFp.setTranslationY(burnInOffsetY); mBgProtection.setAlpha(1f - mInterpolatedDarkAmount); mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount); } else if (darkAmountForAnimation == 0f) { + // we're on the lockscreen and should use mAlpha (changes based on shade expansion) mLockScreenFp.setTranslationX(0); mLockScreenFp.setTranslationY(0); mBgProtection.setAlpha(mAlpha / 255f); @@ -162,9 +161,9 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { } mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount); - mAodFp.setTranslationX(mBurnInOffsetX); - mAodFp.setTranslationY(mBurnInOffsetY); - mAodFp.setProgress(mBurnInProgress); + mAodFp.setTranslationX(burnInOffsetX); + mAodFp.setTranslationY(burnInOffsetY); + mAodFp.setProgress(burnInProgress); mAodFp.setAlpha(mInterpolatedDarkAmount); // done animating diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt new file mode 100644 index 000000000000..5fc510154681 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.data.model + +/** Represents the locked sim card in the Bouncer. */ +data class SimBouncerModel(val isSimPukLocked: Boolean, val subscriptionId: Int) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt new file mode 100644 index 000000000000..3cd88d6044d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.data.model + +/** + * Represents the user flow for unlocking a PUK locked sim card. + * + * After entering the puk code, we need to enter and confirm a new pin code for the sim card. + */ +data class SimPukInputModel( + val enteredSimPuk: String? = null, + val enteredSimPin: String? = null, +) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt new file mode 100644 index 000000000000..ff6321cad670 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.bouncer.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface BouncerRepositoryModule { + @Binds + fun provideSimRepository(simRepositoryImpl: SimBouncerRepositoryImpl): SimBouncerRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt new file mode 100644 index 000000000000..269878b43dab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.data.repository + +import android.annotation.SuppressLint +import android.content.IntentFilter +import android.content.res.Resources +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.bouncer.data.model.SimBouncerModel +import com.android.systemui.bouncer.data.model.SimPukInputModel +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** Handles data layer logic for locked sim cards. */ +interface SimBouncerRepository { + /** The subscription id of the current locked sim card. */ + val subscriptionId: StateFlow<Int> + /** The active subscription of the current subscription id. */ + val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> + /** + * Determines if current sim card is an esim and is locked. + * + * A null value indicates that we do not know if we are esim locked or not. + */ + val isLockedEsim: StateFlow<Boolean?> + /** + * Determines whether the current sim is locked requiring a PUK (Personal Unlocking Key) code. + */ + val isSimPukLocked: StateFlow<Boolean> + /** + * The error message that should be displayed in an alert dialog. + * + * A null value indicates that the error dialog is not showing. + */ + val errorDialogMessage: StateFlow<String?> + /** The state of the user flow on the SimPuk screen. */ + val simPukInputModel: SimPukInputModel + /** Sets the state of the user flow on the SimPuk screen. */ + fun setSimPukUserInput(enteredSimPuk: String? = null, enteredSimPin: String? = null) + /** + * Sets the error message when failing sim verification. + * + * A null value indicates that there is no error message to show. + */ + fun setSimVerificationErrorMessage(msg: String?) +} + +@SysUISingleton +class SimBouncerRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + @Main resources: Resources, + keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val subscriptionManager: SubscriptionManagerProxy, + broadcastDispatcher: BroadcastDispatcher, + euiccManager: EuiccManager, +) : SimBouncerRepository { + private val isPukScreenAvailable: Boolean = + resources.getBoolean(com.android.internal.R.bool.config_enable_puk_unlock_screen) + + private val simBouncerModel: Flow<SimBouncerModel?> = + conflatedCallbackFlow { + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) { + trySend(Unit) + } + } + keyguardUpdateMonitor.registerCallback(callback) + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } + } + .map { + // Check to see if there is a locked sim puk card. + val pukLockedSubId = + withContext(backgroundDispatcher) { + keyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PUK_REQUIRED + ) + } + if ( + isPukScreenAvailable && + subscriptionManager.isValidSubscriptionId(pukLockedSubId) + ) { + return@map (SimBouncerModel(isSimPukLocked = true, pukLockedSubId)) + } + + // If there is no locked sim puk card, check to see if there is a locked sim card. + val pinLockedSubId = + withContext(backgroundDispatcher) { + keyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PIN_REQUIRED + ) + } + if (subscriptionManager.isValidSubscriptionId(pinLockedSubId)) { + return@map SimBouncerModel(isSimPukLocked = false, pinLockedSubId) + } + + return@map null // There is no locked sim. + } + + override val subscriptionId: StateFlow<Int> = + simBouncerModel + .map { state -> state?.subscriptionId ?: INVALID_SUBSCRIPTION_ID } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = INVALID_SUBSCRIPTION_ID, + ) + + @SuppressLint("MissingPermission") + override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> = + subscriptionId + .map { + withContext(backgroundDispatcher) { + subscriptionManager.getActiveSubscriptionInfo(it) + } + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + @SuppressLint("MissingPermission") + override val isLockedEsim: StateFlow<Boolean?> = + activeSubscriptionInfo + .map { info -> info?.let { euiccManager.isEnabled && info.isEmbedded } } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) + + override val isSimPukLocked: StateFlow<Boolean> = + simBouncerModel + .map { it?.isSimPukLocked == true } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + private val disableEsimErrorMessage: Flow<String?> = + broadcastDispatcher.broadcastFlow(filter = IntentFilter(ACTION_DISABLE_ESIM)) { _, receiver + -> + if (receiver.resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { + resources.getString(R.string.error_disable_esim_msg) + } else { + null + } + } + + private val simVerificationErrorMessage: MutableStateFlow<String?> = MutableStateFlow(null) + + override val errorDialogMessage: StateFlow<String?> = + merge(disableEsimErrorMessage, simVerificationErrorMessage) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = null, + ) + + private var _simPukInputModel: SimPukInputModel = SimPukInputModel() + override val simPukInputModel: SimPukInputModel + get() = _simPukInputModel + + override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) { + _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin) + } + + override fun setSimVerificationErrorMessage(msg: String?) { + simVerificationErrorMessage.value = msg + } + + companion object { + const val ACTION_DISABLE_ESIM = "com.android.keyguard.disable_esim" + const val INVALID_SUBSCRIPTION_ID = SubscriptionManager.INVALID_SUBSCRIPTION_ID + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 4e1cddc63530..d5ac48371ae9 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.util.kotlin.pairwise @@ -52,6 +53,8 @@ constructor( private val authenticationInteractor: AuthenticationInteractor, flags: SceneContainerFlags, private val falsingInteractor: FalsingInteractor, + private val powerInteractor: PowerInteractor, + private val simBouncerInteractor: SimBouncerInteractor, ) { /** The user-facing message to show in the bouncer. */ @@ -92,6 +95,10 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + authenticationInteractor.isPinEnhancedPrivacyEnabled + /** Whether the user switcher should be displayed within the bouncer UI on large screens. */ val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible @@ -124,6 +131,7 @@ constructor( * user's pocket or by the user's face while holding their device up to their ear. */ fun onIntentionalUserInput() { + powerInteractor.onUserTouch() falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6)) } @@ -141,6 +149,10 @@ constructor( ) } + fun setMessage(message: String?) { + repository.setMessage(message) + } + /** * Resets the user-facing message back to the default according to the current authentication * method. @@ -179,6 +191,12 @@ constructor( if (input.isEmpty()) { return AuthenticationResult.SKIPPED } + + if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) { + // We authenticate sim in SimInteractor + return AuthenticationResult.SKIPPED + } + // Switching to the application scope here since this method is often called from // view-models, whose lifecycle (and thus scope) is shorter than this interactor. // This allows the task to continue running properly even when the calling scope has been @@ -216,6 +234,7 @@ constructor( private fun promptMessage(authMethod: AuthenticationMethodModel): String { return when (authMethod) { + is AuthenticationMethodModel.Sim -> simBouncerInteractor.getDefaultMessage() is AuthenticationMethodModel.Pin -> applicationContext.getString(R.string.keyguard_enter_your_pin) is AuthenticationMethodModel.Password -> diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt index e398c930e86e..efa77926a423 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import android.content.Intent import android.telecom.TelecomManager +import android.telephony.euicc.EuiccManager import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -47,4 +48,9 @@ object BouncerInteractorModule { ): EmergencyAffordanceManager { return EmergencyAffordanceManager(applicationContext) } + + @Provides + fun provideEuiccManager(@Application applicationContext: Context): EuiccManager { + return applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager + } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt new file mode 100644 index 000000000000..99d1f1370f4f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.domain.interactor + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.os.UserHandle +import android.telephony.PinResult +import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import android.text.TextUtils +import android.util.Log +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.bouncer.data.repository.SimBouncerRepository +import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl +import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl.Companion.ACTION_DISABLE_ESIM +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository +import com.android.systemui.util.icuMessageFormat +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** Handles domain layer logic for locked sim cards. */ +@SuppressLint("WrongConstant") +@SysUISingleton +class SimBouncerInteractor +@Inject +constructor( + @Application private val applicationContext: Context, + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val repository: SimBouncerRepository, + private val telephonyManager: TelephonyManager, + @Main private val resources: Resources, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val euiccManager: EuiccManager, + // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available. + mobileConnectionsRepository: MobileConnectionsRepository, +) { + val subId: StateFlow<Int> = repository.subscriptionId + val isAnySimSecure: Flow<Boolean> = mobileConnectionsRepository.isAnySimSecure + val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim + val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage + + /** Returns the default message for the sim pin screen. */ + fun getDefaultMessage(): String { + val isEsimLocked = repository.isLockedEsim.value ?: false + val isPuk: Boolean = repository.isSimPukLocked.value + val subscriptionId = repository.subscriptionId.value + + if (subscriptionId == INVALID_SUBSCRIPTION_ID) { + Log.e(TAG, "Trying to get default message from unknown sub id") + return "" + } + + var count = telephonyManager.activeModemCount + val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value + val displayName = info?.displayName + var msg: String = + when { + count < 2 && isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint) + count < 2 -> resources.getString(R.string.kg_sim_pin_instructions) + else -> { + when { + !TextUtils.isEmpty(displayName) && isPuk -> + resources.getString(R.string.kg_puk_enter_puk_hint_multi, displayName) + !TextUtils.isEmpty(displayName) -> + resources.getString(R.string.kg_sim_pin_instructions_multi, displayName) + isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint) + else -> resources.getString(R.string.kg_sim_pin_instructions) + } + } + } + + if (isEsimLocked) { + msg = resources.getString(R.string.kg_sim_lock_esim_instructions, msg) + } + + return msg + } + + /** Resets the user flow when the sim screen is puk locked. */ + fun resetSimPukUserInput() { + repository.setSimPukUserInput() + // Force a garbage collection in an attempt to erase any sim pin or sim puk codes left in + // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard + // dismiss animation janky. + + applicationScope.launch(backgroundDispatcher) { + delay(5000) + System.gc() + System.runFinalization() + System.gc() + } + } + + /** Disables the locked esim card so user can bypass the sim pin screen. */ + fun disableEsim() { + val activeSubscription = repository.activeSubscriptionInfo.value + if (activeSubscription == null) { + val subId = repository.subscriptionId.value + Log.e(TAG, "No active subscription with subscriptionId: $subId") + return + } + val intent = Intent(ACTION_DISABLE_ESIM) + intent.setPackage(applicationContext.packageName) + val callbackIntent = + PendingIntent.getBroadcastAsUser( + applicationContext, + 0 /* requestCode */, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE_UNAUDITED, + UserHandle.SYSTEM + ) + applicationScope.launch(backgroundDispatcher) { + euiccManager.switchToSubscription( + INVALID_SUBSCRIPTION_ID, + activeSubscription.portIndex, + callbackIntent, + ) + } + } + + /** Update state when error dialog is dismissed by the user. */ + fun onErrorDialogDismissed() { + repository.setSimVerificationErrorMessage(null) + } + + /** + * Based on sim state, unlock the locked sim with the given credentials. + * + * @return Any message that should show associated with the provided input. Null means that no + * message needs to be shown. + */ + suspend fun verifySim(input: List<Any>): String? { + if (repository.isSimPukLocked.value) { + return verifySimPuk(input.joinToString(separator = "")) + } + + return verifySimPin(input.joinToString(separator = "")) + } + + /** + * Verifies the input and unlocks the locked sim with a 4-8 digit pin code. + * + * @return Any message that should show associated with the provided input. Null means that no + * message needs to be shown. + */ + private suspend fun verifySimPin(input: String): String? { + val subscriptionId = repository.subscriptionId.value + // A SIM PIN is 4 to 8 decimal digits according to + // GSM 02.17 version 5.0.1, Section 5.6 PIN Management + if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) { + return resources.getString(R.string.kg_invalid_sim_pin_hint) + } + val result = + withContext(backgroundDispatcher) { + val telephonyManager: TelephonyManager = + telephonyManager.createForSubscriptionId(subscriptionId) + telephonyManager.supplyIccLockPin(input) + } + when (result.result) { + PinResult.PIN_RESULT_TYPE_SUCCESS -> + keyguardUpdateMonitor.reportSimUnlocked(subscriptionId) + PinResult.PIN_RESULT_TYPE_INCORRECT -> { + if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) { + // Show a dialog to display the remaining number of attempts to verify the sim + // pin to the user. + repository.setSimVerificationErrorMessage( + getPinPasswordErrorMessage(result.attemptsRemaining) + ) + } else { + return getPinPasswordErrorMessage(result.attemptsRemaining) + } + } + } + + return null + } + + /** + * Verifies the input and unlocks the locked sim with a puk code instead of pin. + * + * This occurs after incorrectly verifying the sim pin multiple times. + * + * @return Any message that should show associated with the provided input. Null means that no + * message needs to be shown. + */ + private suspend fun verifySimPuk(entry: String): String? { + val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel + val subscriptionId: Int = repository.subscriptionId.value + + // Stage 1: Enter the sim puk code of the sim card. + if (enteredSimPuk == null) { + if (entry.length >= MIN_SIM_PUK_LENGTH) { + repository.setSimPukUserInput(enteredSimPuk = entry) + return resources.getString(R.string.kg_puk_enter_pin_hint) + } else { + return resources.getString(R.string.kg_invalid_sim_puk_hint) + } + } + + // Stage 2: Set a new sim pin to lock the sim card. + if (enteredSimPin == null) { + if (entry.length in MIN_SIM_PIN_LENGTH..MAX_SIM_PIN_LENGTH) { + repository.setSimPukUserInput( + enteredSimPuk = enteredSimPuk, + enteredSimPin = entry, + ) + return resources.getString(R.string.kg_enter_confirm_pin_hint) + } else { + return resources.getString(R.string.kg_invalid_sim_pin_hint) + } + } + + // Stage 3: Confirm the newly set sim pin. + if (repository.simPukInputModel.enteredSimPin != entry) { + // The entered sim pins do not match. Enter desired sim pin again to confirm. + repository.setSimVerificationErrorMessage( + resources.getString(R.string.kg_invalid_confirm_pin_hint) + ) + repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk) + return resources.getString(R.string.kg_puk_enter_pin_hint) + } + + val result = + withContext(backgroundDispatcher) { + val telephonyManager = telephonyManager.createForSubscriptionId(subscriptionId) + telephonyManager.supplyIccLockPuk(enteredSimPuk, enteredSimPin) + } + resetSimPukUserInput() + + when (result.result) { + PinResult.PIN_RESULT_TYPE_SUCCESS -> + keyguardUpdateMonitor.reportSimUnlocked(subscriptionId) + PinResult.PIN_RESULT_TYPE_INCORRECT -> + if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) { + // Show a dialog to display the remaining number of attempts to verify the sim + // puk to the user. + repository.setSimVerificationErrorMessage( + getPukPasswordErrorMessage( + result.attemptsRemaining, + isDefault = false, + isEsimLocked = repository.isLockedEsim.value == true + ) + ) + } else { + return getPukPasswordErrorMessage( + result.attemptsRemaining, + isDefault = false, + isEsimLocked = repository.isLockedEsim.value == true + ) + } + else -> return resources.getString(R.string.kg_password_puk_failed) + } + + return null + } + + private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String { + var displayMessage: String = + if (attemptsRemaining == 0) { + resources.getString(R.string.kg_password_wrong_pin_code_pukked) + } else if (attemptsRemaining > 0) { + val msgId = R.string.kg_password_default_pin_message + icuMessageFormat(resources, msgId, attemptsRemaining) + } else { + val msgId = R.string.kg_sim_pin_instructions + resources.getString(msgId) + } + if (repository.isLockedEsim.value == true) { + displayMessage = + resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage) + } + return displayMessage + } + + private fun getPukPasswordErrorMessage( + attemptsRemaining: Int, + isDefault: Boolean, + isEsimLocked: Boolean, + ): String { + var displayMessage: String = + if (attemptsRemaining == 0) { + resources.getString(R.string.kg_password_wrong_puk_code_dead) + } else if (attemptsRemaining > 0) { + val msgId = + if (isDefault) R.string.kg_password_default_puk_message + else R.string.kg_password_wrong_puk_code + icuMessageFormat(resources, msgId, attemptsRemaining) + } else { + val msgId = + if (isDefault) R.string.kg_puk_enter_puk_hint + else R.string.kg_password_puk_failed + resources.getString(msgId) + } + if (isEsimLocked) { + displayMessage = + resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage) + } + return displayMessage + } + + companion object { + private const val TAG = "BouncerSimInteractor" + const val INVALID_SUBSCRIPTION_ID = SimBouncerRepositoryImpl.INVALID_SUBSCRIPTION_ID + const val MIN_SIM_PIN_LENGTH = 4 + const val MAX_SIM_PIN_LENGTH = 8 + const val MIN_SIM_PUK_LENGTH = 8 + const val CRITICAL_NUM_OF_ATTEMPTS = 2 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index f46574ca5bbe..80248744c25a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -62,6 +62,13 @@ sealed class AuthMethodBouncerViewModel( /** Notifies that the UI has been shown to the user. */ fun onShown() { + interactor.resetMessage() + } + + /** + * Notifies that the UI has been hidden from the user (after any transitions have completed). + */ + fun onHidden() { clearInput() interactor.resetMessage() } @@ -113,8 +120,6 @@ sealed class AuthMethodBouncerViewModel( } _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED - // TODO(b/291528545): On success, this should only be cleared after the view is animated - // away). clearInput() } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 09c94c81581b..44ddd9740186 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text @@ -64,6 +65,7 @@ class BouncerViewModel( users: Flow<List<UserViewModel>>, userSwitcherMenu: Flow<List<UserActionViewModel>>, actionButtonInteractor: BouncerActionButtonInteractor, + private val simBouncerInteractor: SimBouncerInteractor, ) { val selectedUserImage: StateFlow<Bitmap?> = selectedUser @@ -259,6 +261,17 @@ class BouncerViewModel( viewModelScope = newViewModelScope, interactor = bouncerInteractor, isInputEnabled = isInputEnabled, + simBouncerInteractor = simBouncerInteractor, + authenticationMethod = authenticationMethod + ) + is AuthenticationMethodModel.Sim -> + PinBouncerViewModel( + applicationContext = applicationContext, + viewModelScope = newViewModelScope, + interactor = bouncerInteractor, + isInputEnabled = isInputEnabled, + simBouncerInteractor = simBouncerInteractor, + authenticationMethod = authenticationMethod, ) is AuthenticationMethodModel.Password -> PasswordBouncerViewModel( @@ -316,6 +329,7 @@ object BouncerViewModelModule { flags: SceneContainerFlags, userSwitcherViewModel: UserSwitcherViewModel, actionButtonInteractor: BouncerActionButtonInteractor, + simBouncerInteractor: SimBouncerInteractor, ): BouncerViewModel { return BouncerViewModel( applicationContext = applicationContext, @@ -328,6 +342,7 @@ object BouncerViewModelModule { users = userSwitcherViewModel.users, userSwitcherMenu = userSwitcherViewModel.menu, actionButtonInteractor = actionButtonInteractor, + simBouncerInteractor = simBouncerInteractor, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 2ed0d5d2f6c1..e25e82fe04c3 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -14,20 +14,26 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.bouncer.ui.viewmodel import android.content.Context import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.res.R import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Holds UI state and handles user input for the PIN code bouncer UI. */ class PinBouncerViewModel( @@ -35,13 +41,23 @@ class PinBouncerViewModel( viewModelScope: CoroutineScope, interactor: BouncerInteractor, isInputEnabled: StateFlow<Boolean>, + private val simBouncerInteractor: SimBouncerInteractor, + authenticationMethod: AuthenticationMethodModel, ) : AuthMethodBouncerViewModel( viewModelScope = viewModelScope, interactor = interactor, isInputEnabled = isInputEnabled, ) { - + /** + * Whether the sim-related UI in the pin view is showing. + * + * This UI is used to unlock a locked sim. + */ + val isSimAreaVisible = authenticationMethod == AuthenticationMethodModel.Sim + val isLockedEsim: StateFlow<Boolean?> = simBouncerInteractor.isLockedEsim + val errorDialogMessage: StateFlow<String?> = simBouncerInteractor.errorDialogMessage + val isSimUnlockingDialogVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) val pinShapes = PinShapeAdapter(applicationContext) private val mutablePinInput = MutableStateFlow(PinInputViewModel.empty()) @@ -49,7 +65,13 @@ class PinBouncerViewModel( val pinInput: StateFlow<PinInputViewModel> = mutablePinInput /** The length of the PIN for which we should show a hint. */ - val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength + val hintedPinLength: StateFlow<Int?> = + if (isSimAreaVisible) { + flowOf(null) + } else { + interactor.hintedPinLength + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) /** Appearance of the backspace button. */ val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> = @@ -80,10 +102,32 @@ class PinBouncerViewModel( initialValue = ActionButtonAppearance.Hidden, ) - override val authenticationMethod = AuthenticationMethodModel.Pin + override val authenticationMethod: AuthenticationMethodModel = authenticationMethod override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + init { + viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } } + } + + /** Notifies that the user dismissed the sim pin error dialog. */ + fun onErrorDialogDismissed() { + viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() } + } + + /** + * Whether the digit buttons should be animated when touched. Note that this doesn't affect the + * delete or enter buttons; those should always animate. + */ + val isDigitButtonAnimationEnabled: StateFlow<Boolean> = + interactor.isPinEnhancedPrivacyEnabled + .map { !it } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !interactor.isPinEnhancedPrivacyEnabled.value, + ) + /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { val pinInput = mutablePinInput.value @@ -110,7 +154,28 @@ class PinBouncerViewModel( /** Notifies that the user clicked the "enter" button. */ fun onAuthenticateButtonClicked() { - tryAuthenticate(useAutoConfirm = false) + if (authenticationMethod == AuthenticationMethodModel.Sim) { + viewModelScope.launch { + isSimUnlockingDialogVisible.value = true + val msg = simBouncerInteractor.verifySim(getInput()) + interactor.setMessage(msg) + isSimUnlockingDialogVisible.value = false + clearInput() + } + } else { + tryAuthenticate(useAutoConfirm = false) + } + } + + fun onDisableEsimButtonClicked() { + viewModelScope.launch { simBouncerInteractor.disableEsim() } + } + + /** Resets the sim screen and shows a default message. */ + private fun onResetSimFlow() { + simBouncerInteractor.resetSimPukUserInput() + interactor.resetMessage() + clearInput() } override fun clearInput() { diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 508f52c84983..771dfbcae138 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -141,6 +141,7 @@ constructor( private val umoContent: Flow<List<CommunalContentModel.Umo>> = mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying -> if (mediaPlaying) { + // TODO(b/310254801): support HALF and FULL layouts flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD))) } else { flowOf(emptyList()) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index d8ff535ffd78..a0e944b0ad71 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -22,7 +22,7 @@ import com.android.systemui.LatencyTester import com.android.systemui.ScreenDecorations import com.android.systemui.SliceBroadcastRelayHandler import com.android.systemui.accessibility.SystemActions -import com.android.systemui.accessibility.WindowMagnification +import com.android.systemui.accessibility.Magnification import com.android.systemui.back.domain.interactor.BackActionInteractor import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.BiometricNotificationService @@ -254,11 +254,11 @@ abstract class SystemUICoreStartableModule { @ClassKey(VolumeUI::class) abstract fun bindVolumeUI(sysui: VolumeUI): CoreStartable - /** Inject into WindowMagnification. */ + /** Inject into Magnification. */ @Binds @IntoMap - @ClassKey(WindowMagnification::class) - abstract fun bindWindowMagnification(sysui: WindowMagnification): CoreStartable + @ClassKey(Magnification::class) + abstract fun bindMagnification(sysui: Magnification): CoreStartable /** Inject into WMShell. */ @Binds diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 9fc86adce091..5f54a98fe05d 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -37,13 +37,14 @@ import com.android.systemui.biometrics.FingerprintReEnrollNotification; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule; +import com.android.systemui.bouncer.data.repository.BouncerRepositoryModule; import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule; import com.android.systemui.bouncer.ui.BouncerViewModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; +import com.android.systemui.common.CommonModule; import com.android.systemui.communal.dagger.CommunalModule; import com.android.systemui.complication.dagger.ComplicationComponent; -import com.android.systemui.common.CommonModule; import com.android.systemui.controls.dagger.ControlsModule; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.SystemUser; @@ -59,6 +60,7 @@ import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -120,6 +122,7 @@ import com.android.systemui.statusbar.policy.PolicyModule; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule; import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule; +import com.android.systemui.statusbar.ui.binder.StatusBarViewBinderModule; import com.android.systemui.statusbar.window.StatusBarWindowModule; import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule; import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule; @@ -127,6 +130,7 @@ import com.android.systemui.tuner.dagger.TunerModule; import com.android.systemui.unfold.SysUIUnfoldModule; import com.android.systemui.user.UserModule; import com.android.systemui.user.domain.UserDomainLayerModule; +import com.android.systemui.util.EventLogModule; import com.android.systemui.util.concurrency.SysUIConcurrencyModule; import com.android.systemui.util.dagger.UtilModule; import com.android.systemui.util.kotlin.CoroutinesModule; @@ -169,6 +173,7 @@ import javax.inject.Named; BiometricsModule.class, BiometricsDomainLayerModule.class, BouncerInteractorModule.class, + BouncerRepositoryModule.class, BouncerViewModule.class, ClipboardOverlayModule.class, ClockRegistryModule.class, @@ -182,6 +187,7 @@ import javax.inject.Named; DisableFlagsModule.class, DisplayModule.class, DreamModule.class, + EventLogModule.class, FalsingModule.class, FlagsModule.class, FlagDependenciesModule.class, @@ -189,6 +195,7 @@ import javax.inject.Named; KeyEventRepositoryModule.class, KeyboardModule.class, KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, MediaProjectionModule.class, @@ -215,6 +222,7 @@ import javax.inject.Named; StatusBarModule.class, StatusBarPipelineModule.class, StatusBarPolicyModule.class, + StatusBarViewBinderModule.class, StatusBarWindowModule.class, SystemPropertiesFlagsModule.class, SysUIConcurrencyModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index c3f352932675..715fb17c7c2d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -46,6 +47,7 @@ import kotlinx.coroutines.launch * Device entry occurs when the user successfully dismisses (or bypasses) the lockscreen, regardless * of the authentication method used. */ +@ExperimentalCoroutinesApi @SysUISingleton class DeviceEntryInteractor @Inject @@ -72,7 +74,8 @@ constructor( repository.isUnlocked, authenticationInteractor.authenticationMethod, ) { isUnlocked, authenticationMethod -> - !authenticationMethod.isSecure || isUnlocked + (!authenticationMethod.isSecure || isUnlocked) && + authenticationMethod != AuthenticationMethodModel.Sim } .stateIn( scope = applicationScope, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt new file mode 100644 index 000000000000..72b9da669360 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Encapsulates business logic for device entry under-display fingerprint state changes. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryUdfpsInteractor +@Inject +constructor( + // TODO (b/309655554): create & use interactors for these repositories + fingerprintPropertyRepository: FingerprintPropertyRepository, + fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, + biometricSettingsRepository: BiometricSettingsRepository, +) { + /** Whether the device supports an under display fingerprint sensor. */ + val isUdfpsSupported: Flow<Boolean> = + fingerprintPropertyRepository.sensorType.map { it.isUdfps() } + + /** Whether the under-display fingerprint sensor is enrolled and enabled for device entry. */ + val isUdfpsEnrolledAndEnabled: Flow<Boolean> = + combine(isUdfpsSupported, biometricSettingsRepository.isFingerprintEnrolledAndEnabled) { + udfps, + fpEnrolledAndEnabled -> + udfps && fpEnrolledAndEnabled + } + /** Whether the under display fingerprint sensor is currently running. */ + val isListeningForUdfps = + isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + fingerprintAuthRepository.isRunning + } else { + flowOf(false) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java index 410a0c53a492..ee48ee5f50fd 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java @@ -71,6 +71,7 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { private final Runnable mRestoreComplications = new Runnable() { @Override public void run() { + Log.d(TAG, "Restoring complications..."); mVisibilityController.setVisibility(View.VISIBLE); mHidden = false; } @@ -83,6 +84,7 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { // Avoid interfering with the exit animations. return; } + Log.d(TAG, "Hiding complications..."); mVisibilityController.setVisibility(View.INVISIBLE); mHidden = true; if (mHiddenCallback != null) { @@ -136,9 +138,7 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { final MotionEvent motionEvent = (MotionEvent) ev; if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - if (DEBUG) { - Log.d(TAG, "ACTION_DOWN received"); - } + Log.i(TAG, "ACTION_DOWN received"); final ListenableFuture<Boolean> touchCheck = mTouchInsetManager .checkWithinTouchRegion(Math.round(motionEvent.getX()), @@ -163,6 +163,8 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { }, mExecutor); } else if (motionEvent.getAction() == MotionEvent.ACTION_CANCEL || motionEvent.getAction() == MotionEvent.ACTION_UP) { + Log.i(TAG, "ACTION_CANCEL|ACTION_UP received"); + // End session and initiate delayed reappearance of the complications. session.pop(); runAfterHidden(() -> mCancelCallbacks.add( @@ -179,8 +181,10 @@ public class HideComplicationTouchHandler implements DreamTouchHandler { private void runAfterHidden(Runnable runnable) { mExecutor.execute(() -> { if (mHidden) { + Log.i(TAG, "Executing after hidden runnable immediately..."); runnable.run(); } else { + Log.i(TAG, "Queuing after hidden runnable..."); mHiddenCallback = runnable; } }); diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index da3fd14cdcf0..83c16ae9ea78 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.Flags as Classic import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import javax.inject.Inject @@ -28,9 +27,5 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha FlagDependenciesBase(featureFlags, handler) { override fun defineDependencies() { FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token - - // These two flags are effectively linked. We should migrate them to a single aconfig flag. - Classic.MIGRATE_NSSL dependsOn Classic.MIGRATE_KEYGUARD_STATUS_VIEW - Classic.MIGRATE_KEYGUARD_STATUS_VIEW dependsOn Classic.MIGRATE_NSSL } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index ced96f1aba98..b5fe4c5e7c6f 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -135,14 +135,6 @@ object Flags { "lockscreen_custom_clocks" ) - // TODO(b/286092087): Tracking Bug - @JvmField - val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag("enable_system_ui_dream_controller") - - // TODO(b/288287730): Tracking Bug - @JvmField - val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag("enable_system_ui_dream_hosting") - /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. @@ -254,14 +246,6 @@ object Flags { // TODO(b/287268101): Tracking bug. @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock") - /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */ - // TODO(b/288074305): Tracking bug. - @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl") - - /** Migrate the status view from the notification panel to keyguard root view. */ - // TODO(b/291767565): Tracking bug. - @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view") - /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */ // TODO(b/299115332): Tracking Bug. @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW = @@ -316,11 +300,6 @@ object Flags { val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED = releasedFlag("smartspace_shared_element_transition_enabled") - // TODO(b/258517050): Clean up after the feature is launched. - @JvmField - val SMARTSPACE_DATE_WEATHER_DECOUPLED = - sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true) - // TODO(b/270223352): Tracking Bug @JvmField val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 8b93b171d241..331d892304b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -55,6 +55,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger; import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl; +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -94,6 +95,7 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardStatusViewComponent.class, KeyguardUserSwitcherComponent.class}, includes = { + DeviceEntryIconTransitionModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 8d5d73f88ca1..5c76be80f1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -49,8 +49,10 @@ import kotlinx.coroutines.flow.filter * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on * this repository. * - * To print all transitions to logcat to help with debugging, run this command: adb shell settings - * put global systemui/buffer/KeyguardLog VERBOSE + * To print all transitions to logcat to help with debugging, run this command: + * ``` + * adb shell cmd statusbar echo -b KeyguardLog:VERBOSE + * ``` * * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag. */ @@ -175,9 +177,11 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio override fun onAnimationStart(animation: Animator) { emitTransition(TransitionStep(info, startingValue, TransitionState.STARTED)) } + override fun onAnimationCancel(animation: Animator) { endAnimation(lastStep.value, TransitionState.CANCELED) } + override fun onAnimationEnd(animation: Animator) { endAnimation(1f, TransitionState.FINISHED) } @@ -222,7 +226,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio } private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) { - trace(nextStep, isManual) + logAndTrace(nextStep, isManual) val emitted = _transitions.tryEmit(nextStep) if (!emitted) { Log.w(TAG, "Failed to emit next value without suspending") @@ -230,26 +234,22 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio lastStep = nextStep } - private fun trace(step: TransitionStep, isManual: Boolean) { + private fun logAndTrace(step: TransitionStep, isManual: Boolean) { if (step.transitionState == TransitionState.RUNNING) { return } - val traceName = - "Transition: ${step.from} -> ${step.to} " + - if (isManual) { - "(manual)" - } else { - "" - } + val manualStr = if (isManual) " (manual)" else "" + val traceName = "Transition: ${step.from} -> ${step.to}$manualStr" + val traceCookie = traceName.hashCode() - if (step.transitionState == TransitionState.STARTED) { - Trace.beginAsyncSection(traceName, traceCookie) - } else if ( - step.transitionState == TransitionState.FINISHED || - step.transitionState == TransitionState.CANCELED - ) { - Trace.endAsyncSection(traceName, traceCookie) + when (step.transitionState) { + TransitionState.STARTED -> Trace.beginAsyncSection(traceName, traceCookie) + TransitionState.FINISHED -> Trace.endAsyncSection(traceName, traceCookie) + TransitionState.CANCELED -> Trace.endAsyncSection(traceName, traceCookie) + else -> {} } + + Log.i(TAG, "${step.transitionState.name} transition: $step$manualStr") } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index 8bf2bc395f88..cc1cf911f1c1 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 @@ -50,14 +50,18 @@ constructor( private val configurationRepository: ConfigurationRepository, private val keyguardInteractor: KeyguardInteractor, ) { - val udfpsBurnInXOffset: StateFlow<Int> = + val deviceEntryIconXOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) - val udfpsBurnInYOffset: StateFlow<Int> = + val deviceEntryIconYOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) - val udfpsBurnInProgress: StateFlow<Float> = + val udfpsProgress: StateFlow<Float> = keyguardInteractor.dozeTimeTick .mapLatest { burnInHelperWrapper.burnInProgressOffset() } - .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + burnInHelperWrapper.burnInProgressOffset() + ) val keyguardBurnIn: Flow<BurnInModel> = combine( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index a331a668e135..858440185568 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -113,7 +113,9 @@ constructor( } companion object { - val TO_LOCKSCREEN_DURATION = 500.milliseconds private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = 500.milliseconds + val TO_GONE_DURATION = DEFAULT_DURATION + val TO_OCCLUDED_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index e9719e7d584e..eca7088c079a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -26,11 +26,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor 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.flow.combine import kotlinx.coroutines.launch -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds @SysUISingleton class FromDozingTransitionInteractor @@ -97,5 +97,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } 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 ca882e539a1a..0b6b971f2314 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -54,7 +54,10 @@ constructor( fun startToLockscreenTransition() { scope.launch { - if (transitionInteractor.startedKeyguardState.value == KeyguardState.DREAMING) { + if ( + transitionInteractor.startedKeyguardState.replayCache.last() == + KeyguardState.DREAMING + ) { startTransitionTo(KeyguardState.LOCKSCREEN) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index eace0c70cb5b..bd73d60cda29 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 @@ -138,6 +138,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds - val TO_AOD_DURATION = 1100.milliseconds + val TO_AOD_DURATION = 1300.milliseconds } } 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 ea40ba0376d4..152d2172ee4c 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 @@ -391,5 +391,7 @@ constructor( val TO_DREAMING_DURATION = 933.milliseconds val TO_OCCLUDED_DURATION = 450.milliseconds val TO_AOD_DURATION = 500.milliseconds + val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION + val TO_GONE_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index dec38b504ee1..6a8555cb7f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -142,9 +142,7 @@ constructor( ::toTriple ) .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if ( - lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep - ) { + if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) { startTransitionTo( if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING ) @@ -187,5 +185,6 @@ constructor( companion object { private val DEFAULT_DURATION = 500.milliseconds val TO_LOCKSCREEN_DURATION = 933.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION } } 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 24b666185ce2..5f246e181c26 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 @@ -255,5 +255,7 @@ constructor( private val DEFAULT_DURATION = 300.milliseconds val TO_GONE_DURATION = 500.milliseconds val TO_GONE_SHORT_DURATION = 200.milliseconds + val TO_AOD_DURATION = DEFAULT_DURATION + val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt index efbe2611e960..922baa3611cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -24,6 +24,7 @@ import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -40,18 +41,20 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) val viewParams: Flow<KeyguardSurfaceBehindModel> = - transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition -> - if (!isInTransition) { - defaultParams - } else { - combine( - transitionSpecificViewParams, - defaultParams, - ) { transitionParams, defaultParams -> - transitionParams ?: defaultParams + transitionInteractor.isInTransitionToAnyState + .flatMapLatest { isInTransition -> + if (!isInTransition) { + defaultParams + } else { + combine( + transitionSpecificViewParams, + defaultParams, + ) { transitionParams, defaultParams -> + transitionParams ?: defaultParams + } } } - } + .distinctUntilChanged() val isAnimatingSurface = repository.isAnimatingSurface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 419524fe7730..a03fa38ec850 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -75,24 +75,6 @@ constructor( } scope.launch { - interactor.finishedKeyguardTransitionStep.collect { - logger.log(TAG, VERBOSE, "Finished transition", it) - } - } - - scope.launch { - interactor.canceledKeyguardTransitionStep.collect { - logger.log(TAG, VERBOSE, "Canceled transition", it) - } - } - - scope.launch { - interactor.startedKeyguardTransitionStep.collect { - logger.log(TAG, VERBOSE, "Started transition", it) - } - } - - scope.launch { keyguardInteractor.dozeTransitionModel.collect { logger.log(TAG, VERBOSE, "Doze transition", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index b0b857729c77..4da48f697b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -37,14 +37,14 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.shareIn /** Encapsulates business-logic related to the keyguard transitions. */ @SysUISingleton @@ -171,16 +171,16 @@ constructor( repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } /** The destination state of the last started transition. */ - val startedKeyguardState: StateFlow<KeyguardState> = + val startedKeyguardState: SharedFlow<KeyguardState> = startedKeyguardTransitionStep .map { step -> step.to } - .stateIn(scope, SharingStarted.Eagerly, OFF) + .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** The last completed [KeyguardState] transition */ - val finishedKeyguardState: StateFlow<KeyguardState> = + val finishedKeyguardState: SharedFlow<KeyguardState> = finishedKeyguardTransitionStep .map { step -> step.to } - .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN) + .shareIn(scope, SharingStarted.Eagerly, replay = 1) /** * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed @@ -227,14 +227,13 @@ constructor( * state. */ fun startDismissKeyguardTransition() { - when (startedKeyguardState.value) { + when (val startedState = startedKeyguardState.replayCache.last()) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() else -> Log.e( "KeyguardTransitionInteractor", - "We don't know how to dismiss keyguard from state " + - "${startedKeyguardState.value}" + "We don't know how to dismiss keyguard from state $startedState." ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt new file mode 100644 index 000000000000..508fb597ef65 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.content.Context +import android.database.ContentObserver +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.provider.Settings.SettingNotFoundException +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import javax.inject.Inject + +@SysUISingleton +class NaturalScrollingSettingObserver +@Inject +constructor( + @Main private val handler: Handler, + private val context: Context, +) { + var isNaturalScrollingEnabled = true + get() { + if (!isInitialized) { + isInitialized = true + update() + } + return field + } + + private var isInitialized = false + + private val contentObserver = object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean) { + update() + } + } + + init { + context.contentResolver.registerContentObserver( + Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), false, + contentObserver) + } + + private fun update() { + isNaturalScrollingEnabled = try { + Settings.System.getIntForUser(context.contentResolver, + Settings.System.TOUCHPAD_NATURAL_SCROLLING, UserHandle.USER_CURRENT) == 1 + } catch (e: SettingNotFoundException) { + true + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 76018080848f..d5ac2838a2f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -53,16 +53,16 @@ sealed class TransitionInteractor( modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE ): UUID? { if ( - fromState != transitionInteractor.startedKeyguardState.value && - fromState != transitionInteractor.finishedKeyguardState.value + fromState != transitionInteractor.startedKeyguardState.replayCache.last() && + fromState != transitionInteractor.finishedKeyguardState.replayCache.last() ) { Log.e( name, "startTransition: We were asked to transition from " + "$fromState to $toState, however we last finished a transition to " + - "${transitionInteractor.finishedKeyguardState.value}, " + + "${transitionInteractor.finishedKeyguardState.replayCache.last()}, " + "and last started a transition to " + - "${transitionInteractor.startedKeyguardState.value}. " + + "${transitionInteractor.startedKeyguardState.replayCache.last()}. " + "Ignoring startTransition, but this should never happen." ) return null diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt index c0308e6c5759..f5cd7676e4b1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -51,14 +51,14 @@ constructor( val scaleForResolution = configRepo.scaleForResolution /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ - val burnInOffsets: Flow<BurnInOffsets> = + val burnInOffsets: Flow<Offsets> = combine( keyguardInteractor.dozeAmount, - burnInInteractor.udfpsBurnInXOffset, - burnInInteractor.udfpsBurnInYOffset, - burnInInteractor.udfpsBurnInProgress + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> - BurnInOffsets( + Offsets( intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), @@ -86,8 +86,8 @@ constructor( .onStart { emit(0f) } } -data class BurnInOffsets( - val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount - val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount - val burnInProgress: Float, // current progress based on the aodTransitionAmount +data class Offsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt new file mode 100644 index 000000000000..23642a741fb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/KeyguardShadeMigrationNssl.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.keyguard.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the keyguard shade migration nssl flag state. */ +@Suppress("NOTHING_TO_INLINE") +object KeyguardShadeMigrationNssl { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.keyguardShadeMigrationNssl() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 9d7477c13be6..d5ad7ab0d0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -105,4 +105,9 @@ class KeyguardTransitionAnimationFlow( } .filterNotNull() } + + /** Immediately (after 1ms) emits the given value for every step of the KeyguardTransition. */ + fun immediatelyTransitionTo(value: Float): Flow<Float> { + return createFlow(duration = 1.milliseconds, onStep = { value }, onFinish = { value }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index e82ea7fecd05..a8b28bcfbbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -24,6 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager @@ -34,19 +36,24 @@ import kotlinx.coroutines.launch object DeviceEntryIconViewBinder { /** - * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its - * background. + * Updates UI for: + * - device entry containing view (parent view for the below views) + * - long-press handling view (transparent, no UI) + * - foreground icon view (lock/unlock/fingerprint) + * - background view (optional) */ @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: DeviceEntryIconView, viewModel: DeviceEntryIconViewModel, + fgViewModel: DeviceEntryForegroundViewModel, + bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, ) { - val iconView = view.iconView - val bgView = view.bgView val longPressHandlingView = view.longPressHandlingView + val fgIconView = view.iconView + val bgView = view.bgView longPressHandlingView.listener = object : LongPressHandlingView.Listener { override fun onLongPressDetected(view: View, x: Int, y: Int) { @@ -56,45 +63,69 @@ object DeviceEntryIconViewBinder { viewModel.onLongPress() } } + view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { + // Repeat on CREATED so that the view will always observe the entire + // GONE => AOD transition (even though the view may not be visible until the middle + // of the transition. + repeatOnLifecycle(Lifecycle.State.CREATED) { launch { - viewModel.iconViewModel.collect { iconViewModel -> - iconView.setImageState( - view.getIconState(iconViewModel.type, iconViewModel.useAodVariant), - /* merge */ false - ) - iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint) - iconView.alpha = iconViewModel.alpha - iconView.setPadding( - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - iconViewModel.padding, - ) + viewModel.isLongPressEnabled.collect { isEnabled -> + longPressHandlingView.setLongPressHandlingEnabled(isEnabled) } } launch { - viewModel.backgroundViewModel.collect { bgViewModel -> - bgView.alpha = bgViewModel.alpha - bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + viewModel.accessibilityDelegateHint.collect { hint -> + view.accessibilityHintType = hint } } launch { - viewModel.burnInViewModel.collect { burnInViewModel -> - view.translationX = burnInViewModel.x.toFloat() - view.translationY = burnInViewModel.y.toFloat() - view.aodFpDrawable.progress = burnInViewModel.progress + viewModel.useBackgroundProtection.collect { useBackgroundProtection -> + if (useBackgroundProtection) { + bgView.visibility = View.VISIBLE + } else { + bgView.visibility = View.GONE + } } } launch { - viewModel.isLongPressEnabled.collect { isEnabled -> - longPressHandlingView.setLongPressHandlingEnabled(isEnabled) + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() + view.aodFpDrawable.progress = burnInOffsets.progress } } + + launch { viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha } } + } + } + + fgIconView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.accessibilityDelegateHint.collect { hint -> - view.accessibilityHintType = hint + fgViewModel.viewModel.collect { viewModel -> + fgIconView.setImageState( + view.getIconState(viewModel.type, viewModel.useAodVariant), + /* merge */ false + ) + fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + fgIconView.setPadding( + viewModel.padding, + viewModel.padding, + viewModel.padding, + viewModel.padding, + ) + } + } + } + } + + bgView.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + bgViewModel.viewModel.collect { bgViewModel -> + bgView.alpha = bgViewModel.alpha + bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt deleted file mode 100644 index 5900a2467994..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.binder - -import android.view.View -import android.view.ViewGroup -import android.view.ViewPropertyAnimator -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R -import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel -import com.android.systemui.lifecycle.repeatWhenAttached -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -object KeyguardAmbientIndicationAreaViewBinder { - /** - * Defines interface for an object that acts as the binding between the view and its view-model. - * - * Users of the [KeyguardBottomAreaViewBinder] class should use this to control the binder after - * it is bound. - */ - interface Binding { - /** - * Returns a collection of [ViewPropertyAnimator] instances that can be used to animate the - * indication areas. - */ - fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> - - /** Notifies that device configuration has changed. */ - fun onConfigurationChanged() - - /** Destroys this binding, releases resources, and cancels any coroutines. */ - fun destroy() - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun bind( - view: ViewGroup, - viewModel: KeyguardAmbientIndicationViewModel, - keyguardRootViewModel: KeyguardRootViewModel, - ): Binding { - val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container) - val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) - - val disposableHandle = - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - keyguardRootViewModel.alpha.collect { alpha -> - ambientIndicationArea?.apply { - this.importantForAccessibility = - if (alpha == 0f) { - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - } else { - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO - } - this.alpha = alpha - } - } - } - - launch { - viewModel.indicationAreaTranslationX.collect { translationX -> - ambientIndicationArea?.translationX = translationX - } - } - - launch { - configurationBasedDimensions - .map { it.defaultBurnInPreventionYOffsetPx } - .flatMapLatest { defaultBurnInOffsetY -> - viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) - } - .collect { translationY -> - ambientIndicationArea?.translationY = translationY - } - } - - } - } - - - return object : Binding { - override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { - return listOf(ambientIndicationArea).mapNotNull { it?.animate() } - } - - override fun onConfigurationChanged() { - configurationBasedDimensions.value = loadFromResources(view) - } - - override fun destroy() { - disposableHandle.dispose() - } - } - } - - private fun loadFromResources(view: View): ConfigurationBasedDimensions { - return ConfigurationBasedDimensions( - defaultBurnInPreventionYOffsetPx = - view.resources.getDimensionPixelOffset(R.dimen.default_burn_in_prevention_offset), - ) - } - - private data class ConfigurationBasedDimensions( - val defaultBurnInPreventionYOffsetPx: Int, - ) -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 1f74bb661135..4de9fc343af1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -41,7 +41,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.flags.RefactorFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel @@ -116,7 +116,7 @@ object KeyguardRootViewBinder { launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } } - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { launch { viewModel.burnInLayerVisibility.collect { visibility -> childViews[burnInLayerId]?.visibility = visibility @@ -342,7 +342,7 @@ object KeyguardRootViewBinder { featureFlags: FeatureFlagsClassic, screenOffAnimationController: ScreenOffAnimationController, ): DisposableHandle? { - RefactorFlag(featureFlags, Flags.MIGRATE_KEYGUARD_STATUS_VIEW).assertInLegacyMode() + KeyguardShadeMigrationNssl.assertInLegacyMode() if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return null return view.repeatWhenAttached { lifecycleScope.launch { @@ -368,7 +368,7 @@ object KeyguardRootViewBinder { iconsAppearTranslationPx: Int, screenOffAnimationController: ScreenOffAnimationController, ) { - val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW) + val statusViewMigrated = KeyguardShadeMigrationNssl.isEnabled animate().cancel() val animatorListener = object : AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt index 9872d97021fa..52d87d369083 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -42,9 +42,9 @@ object UdfpsAodFingerprintViewBinder { repeatOnLifecycle(Lifecycle.State.STARTED) { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.progress = burnInOffsets.burnInProgress - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.progress = burnInOffsets.progress + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt index bab04f234b3f..d4621e6e2356 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -59,8 +59,8 @@ object UdfpsFingerprintViewBinder { launch { viewModel.burnInOffsets.collect { burnInOffsets -> - view.translationX = burnInOffsets.burnInXOffset.toFloat() - view.translationY = burnInOffsets.burnInYOffset.toFloat() + view.translationX = burnInOffsets.x.toFloat() + view.translationY = burnInOffsets.y.toFloat() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index bdd9a6bf3f79..a2e930c49511 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -49,7 +49,7 @@ import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -445,7 +445,7 @@ constructor( private fun setUpClock(previewContext: Context, parentView: ViewGroup) { largeClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view_large) } else { val hostView = FrameLayout(previewContext) @@ -460,7 +460,7 @@ constructor( largeClockHostView.isInvisible = true smallClockHostView = - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { parentView.requireViewById<FrameLayout>(R.id.lockscreen_clock_view) } else { val resources = parentView.resources diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt new file mode 100644 index 000000000000..b58a80ff8d34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransition.kt @@ -0,0 +1,30 @@ +/* + * 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.transitions + +import kotlinx.coroutines.flow.Flow + +/** + * Each DeviceEntryIconTransition is responsible for updating the given parameters for the current + * keyguard transition. + * * + * MUST list implementing classes in dagger module [DeviceEntryIconTransitionModule]. + */ +interface DeviceEntryIconTransition { + val deviceEntryParentViewAlpha: Flow<Float> +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt new file mode 100644 index 000000000000..9d557bb8a3b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/DeviceEntryIconTransitionModule.kt @@ -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 com.android.systemui.keyguard.ui.transitions + +import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@Module +abstract class DeviceEntryIconTransitionModule { + @Binds + @IntoSet + abstract fun aodToLockscreen( + impl: AodToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun aodToGone(impl: AodToGoneTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dozingToLockscreen( + impl: DozingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dreamingToLockscreen( + impl: DreamingToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToAod( + impl: LockscreenToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToDreaming( + impl: LockscreenToDreamingTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToOccluded( + impl: LockscreenToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToPrimaryBouncer( + impl: LockscreenToPrimaryBouncerTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun lockscreenToGone( + impl: LockscreenToGoneTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun goneToAod(impl: GoneToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToAod(impl: OccludedToAodTransitionViewModel): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToLockscreen( + impl: OccludedToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToAod( + impl: PrimaryBouncerToAodTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun primaryBouncerToLockscreen( + impl: PrimaryBouncerToLockscreenTransitionViewModel + ): DeviceEntryIconTransition +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index c9e395402dad..af1d0df92652 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -40,7 +40,10 @@ constructor( attrs: AttributeSet?, defStyleAttrs: Int = 0, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + val longPressHandlingView: LongPressHandlingView = + LongPressHandlingView(context, attrs) { + context.resources.getInteger(R.integer.config_lockIconLongPress).toLong() + } val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() @@ -105,7 +108,7 @@ constructor( // FINGERPRINT animatedIconDrawable.addState( getIconState(IconType.FINGERPRINT, false), - context.getDrawable(R.drawable.ic_kg_fingerprint)!!, + context.getDrawable(R.drawable.ic_fingerprint)!!, R.id.locked_fp, ) @@ -220,7 +223,7 @@ constructor( val lp = longPressHandlingView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - longPressHandlingView.setLayoutParams(lp) + longPressHandlingView.layoutParams = lp } private fun addIconImageView() { @@ -231,7 +234,7 @@ constructor( lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT lp.gravity = Gravity.CENTER - iconView.setLayoutParams(lp) + iconView.layoutParams = lp } private fun addBgImageView() { @@ -240,7 +243,7 @@ constructor( val lp = bgView.layoutParams as LayoutParams lp.height = ViewGroup.LayoutParams.MATCH_PARENT lp.width = ViewGroup.LayoutParams.MATCH_PARENT - bgView.setLayoutParams(lp) + bgView.layoutParams = lp } fun getIconState(icon: IconType, aod: Boolean): IntArray { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 2e64c41bace8..0cf891c3f665 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -20,10 +20,10 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection @@ -31,8 +31,13 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection +import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines +import java.util.Optional import javax.inject.Inject +import javax.inject.Named +import kotlin.jvm.optionals.getOrNull /** * Positions elements of the lockscreen to the default position. @@ -47,7 +52,8 @@ constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusViewSection: DefaultStatusViewSection, defaultStatusBarSection: DefaultStatusBarSection, @@ -61,11 +67,11 @@ constructor( override val id: String = DEFAULT override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultShortcutsSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, defaultStatusViewSection, defaultStatusBarSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index d8b368b4a0d3..14e8f892e101 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -19,18 +19,22 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines +import com.android.systemui.util.kotlin.getOrNull +import java.util.Optional import javax.inject.Inject +import javax.inject.Named /** Vertically aligns the shortcuts with the udfps. */ @SysUISingleton @@ -39,7 +43,8 @@ class ShortcutsBesideUdfpsKeyguardBlueprint constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, defaultStatusViewSection: DefaultStatusViewSection, @@ -52,10 +57,10 @@ constructor( override val id: String = SHORTCUTS_BESIDE_UDFPS override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection, defaultStatusViewSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt index 35679b84771b..0d397bff72ce 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt @@ -20,18 +20,22 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection +import com.android.systemui.util.kotlin.getOrNull +import java.util.Optional import javax.inject.Inject +import javax.inject.Named /** * Split-shade layout, mostly used for larger devices like foldables and tablets when in landscape @@ -45,7 +49,8 @@ constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, - defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, + @Named(KeyguardSectionsModule.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + defaultAmbientIndicationAreaSection: Optional<KeyguardSection>, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, defaultStatusViewSection: DefaultStatusViewSection, defaultStatusBarSection: DefaultStatusBarSection, @@ -58,11 +63,11 @@ constructor( override val id: String = ID override val sections = - listOf( + listOfNotNull( defaultIndicationAreaSection, defaultDeviceEntryIconSection, defaultShortcutsSection, - defaultAmbientIndicationAreaSection, + defaultAmbientIndicationAreaSection.getOrNull(), defaultSettingsPopupMenuSection, defaultStatusViewSection, defaultStatusBarSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt index eb01d4f6f61c..b7a165c212fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt @@ -28,6 +28,8 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel @@ -46,6 +48,7 @@ constructor( private val falsingManager: FalsingManager, private val indicationController: KeyguardIndicationController, private val vibratorHelper: VibratorHelper, + private val featureFlags: FeatureFlagsClassic, ) : BaseShortcutSection() { override fun addViews(constraintLayout: ConstraintLayout) { if (keyguardBottomAreaRefactor()) { @@ -83,20 +86,26 @@ constructor( val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width) val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height) + val lockIconViewId = if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + constraintSet.apply { constrainWidth(R.id.start_button, width) constrainHeight(R.id.start_button, height) connect(R.id.start_button, LEFT, PARENT_ID, LEFT) - connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT) - connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP) - connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM) + connect(R.id.start_button, RIGHT, lockIconViewId, LEFT) + connect(R.id.start_button, TOP, lockIconViewId, TOP) + connect(R.id.start_button, BOTTOM, lockIconViewId, BOTTOM) constrainWidth(R.id.end_button, width) constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT) - connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT) - connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP) - connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM) + connect(R.id.end_button, LEFT, lockIconViewId, RIGHT) + connect(R.id.end_button, TOP, lockIconViewId, TOP) + connect(R.id.end_button, BOTTOM, lockIconViewId, BOTTOM) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 09caf4505c3b..484d351a362e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -22,8 +22,7 @@ import android.view.View import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import javax.inject.Inject @@ -33,11 +32,10 @@ class AodBurnInSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } @@ -53,13 +51,13 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 975d62a0b9e3..12de185488f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -29,14 +29,15 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor -import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController @@ -49,8 +50,8 @@ constructor( private val context: Context, private val configurationState: ConfigurationState, private val configurationController: ConfigurationController, - private val dozeParameters: DozeParameters, private val featureFlags: FeatureFlagsClassic, + private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val notificationIconAreaController: NotificationIconAreaController, @@ -62,7 +63,7 @@ constructor( private lateinit var nic: NotificationIconContainer override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } nic = @@ -81,12 +82,11 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } if (NotificationIconContainerRefactor.isEnabled) { - nic.setOnLockScreen(true) nicBindingDisposable?.dispose() nicBindingDisposable = NotificationIconContainerViewBinder.bind( @@ -94,6 +94,7 @@ constructor( nicAodViewModel, configurationState, configurationController, + iconBindingFailureTracker, nicAodIconViewStore, ) } else { @@ -102,7 +103,7 @@ constructor( } override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } val bottomMargin = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt deleted file mode 100644 index 20cb9b0576db..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.view.layout.sections - -import android.view.LayoutInflater -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM -import androidx.constraintlayout.widget.ConstraintSet.END -import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT -import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID -import androidx.constraintlayout.widget.ConstraintSet.START -import androidx.constraintlayout.widget.ConstraintSet.TOP -import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags.keyguardBottomAreaRefactor -import com.android.systemui.res.R -import com.android.systemui.keyguard.shared.model.KeyguardSection -import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel -import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel -import javax.inject.Inject - -class DefaultAmbientIndicationAreaSection -@Inject -constructor( - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel, - private val keyguardRootViewModel: KeyguardRootViewModel, -) : KeyguardSection() { - private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null - - override fun addViews(constraintLayout: ConstraintLayout) { - if (keyguardBottomAreaRefactor()) { - val view = - LayoutInflater.from(constraintLayout.context) - .inflate(R.layout.ambient_indication, constraintLayout, false) - - constraintLayout.addView(view) - } - } - - override fun bindData(constraintLayout: ConstraintLayout) { - if (keyguardBottomAreaRefactor()) { - ambientIndicationAreaHandle = - KeyguardAmbientIndicationAreaViewBinder.bind( - constraintLayout, - keyguardAmbientIndicationViewModel, - keyguardRootViewModel, - ) - } - } - - override fun applyConstraints(constraintSet: ConstraintSet) { - constraintSet.apply { - constrainWidth(R.id.ambient_indication_container, MATCH_PARENT) - - if (keyguardUpdateMonitor.isUdfpsSupported) { - // constrain below udfps and above indication area - constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT) - connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM) - connect( - R.id.ambient_indication_container, - BOTTOM, - R.id.keyguard_indication_area, - TOP - ) - connect(R.id.ambient_indication_container, START, PARENT_ID, START) - connect(R.id.ambient_indication_container, END, PARENT_ID, END) - } else { - // constrain above lock icon - constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT) - connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP) - connect(R.id.ambient_indication_container, START, PARENT_ID, START) - connect(R.id.ambient_indication_container, END, PARENT_ID, END) - } - } - } - - override fun removeViews(constraintLayout: ConstraintLayout) { - ambientIndicationAreaHandle?.destroy() - - constraintLayout.removeView(R.id.ambient_indication_container) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index ace970a01054..13ea8ff8e388 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -36,6 +36,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -56,12 +58,15 @@ constructor( private val featureFlags: FeatureFlags, private val lockIconViewController: Lazy<LockIconViewController>, private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, + private val deviceEntryForegroundViewModel: Lazy<DeviceEntryForegroundViewModel>, + private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!keyguardBottomAreaRefactor() && + if ( + !keyguardBottomAreaRefactor() && !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) ) { return @@ -87,6 +92,8 @@ constructor( DeviceEntryIconViewBinder.bind( it, deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), falsingManager.get(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index c2aedca4ffbc..165ee364c2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -40,7 +41,7 @@ class DefaultNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -50,7 +51,6 @@ constructor( ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, @@ -58,7 +58,7 @@ constructor( notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt index 0b0f21d7a281..4abcca9d1151 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt @@ -31,9 +31,8 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.keyguard.KeyguardStatusView import com.android.keyguard.dagger.KeyguardStatusViewComponent -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardViewConfigurator +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.media.controls.ui.KeyguardMediaController import com.android.systemui.res.R @@ -49,7 +48,6 @@ class DefaultStatusViewSection @Inject constructor( private val context: Context, - private val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>, @@ -60,7 +58,7 @@ constructor( private val statusViewId = R.id.keyguard_status_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available. @@ -82,7 +80,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled) { constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let { val statusViewComponent = keyguardStatusViewComponentFactory.build(it, context.display) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt new file mode 100644 index 000000000000..37c00b61c4dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSectionsModule.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view.layout.sections + +import com.android.systemui.keyguard.shared.model.KeyguardSection +import dagger.BindsOptionalOf +import dagger.Module +import javax.inject.Named + +@Module +abstract class KeyguardSectionsModule { + + companion object { + const val KEYGUARD_AMBIENT_INDICATION_AREA_SECTION = + "keyguard_ambient_indication_area_section" + } + + @BindsOptionalOf + @Named(KEYGUARD_AMBIENT_INDICATION_AREA_SECTION) + abstract fun defaultAmbientIndicationAreaSection(): KeyguardSection + +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index ea2bdf79a114..441f59d6df1d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -21,8 +21,7 @@ import android.content.Context import android.view.View import android.view.ViewGroup import androidx.constraintlayout.widget.ConstraintLayout -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -36,7 +35,6 @@ import kotlinx.coroutines.DisposableHandle abstract class NotificationStackScrollLayoutSection constructor( protected val context: Context, - protected val featureFlags: FeatureFlags, private val notificationPanelView: NotificationPanelView, private val sharedNotificationContainer: SharedNotificationContainer, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -47,7 +45,7 @@ constructor( private var disposableHandle: DisposableHandle? = null override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } // This moves the existing NSSL view to a different parent, as the controller is a @@ -62,7 +60,7 @@ constructor( } override fun bindData(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } disposableHandle?.dispose() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index dc2ad8d8f718..2c45da63edb4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -40,7 +41,7 @@ class SplitShadeNotificationStackScrollLayoutSection @Inject constructor( context: Context, - featureFlags: FeatureFlags, + private val featureFlags: FeatureFlags, notificationPanelView: NotificationPanelView, sharedNotificationContainer: SharedNotificationContainer, sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, @@ -50,7 +51,6 @@ constructor( ) : NotificationStackScrollLayoutSection( context, - featureFlags, notificationPanelView, sharedNotificationContainer, sharedNotificationContainerViewModel, @@ -58,7 +58,7 @@ constructor( notificationStackSizeCalculator, ) { override fun applyConstraints(constraintSet: ConstraintSet) { - if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { return } constraintSet.apply { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..4d2af0c7bd4d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.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.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Breaks down AOD->GONE transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class AodToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index 024707ad2885..14de01b41867 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -17,22 +17,29 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class AodToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -47,4 +54,21 @@ constructor( onStart = { 1f }, onStep = { 1f }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // fade in + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + // background view isn't visible, so return an empty flow + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..06661d0a466b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -0,0 +1,41 @@ +/* + * 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.FromAodTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject + +/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class AodToOccludedTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, + transitionFlow = interactor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED), + ) + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt new file mode 100644 index 000000000000..3e8bbb3b4c34 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon background view. */ +@ExperimentalCoroutinesApi +class DeviceEntryBackgroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel, + aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, + occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, +) { + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorSurface) + } + .onStart { + emit( + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.colorSurface + ) + ) + } + private val alpha: Flow<Float> = + setOf( + lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha, + occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha, + ) + .merge() + + val viewModel: Flow<BackgroundViewModel> = + combine(color, alpha) { color, alpha -> + BackgroundViewModel( + alpha = alpha, + tint = color, + ) + } + + data class BackgroundViewModel( + val alpha: Float, + val tint: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt new file mode 100644 index 000000000000..99529a100b07 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt @@ -0,0 +1,92 @@ +/* + * 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 android.content.Context +import com.android.settingslib.Utils +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.res.R +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** Models the UI state for the device entry icon foreground view (displayed icon). */ +@ExperimentalCoroutinesApi +class DeviceEntryForegroundViewModel +@Inject +constructor( + val context: Context, + configurationRepository: ConfigurationRepository, // TODO (b/309655554): create & use interactor + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + deviceEntryIconViewModel: DeviceEntryIconViewModel, +) { + private val isShowingAod: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.AOD + } + private val color: Flow<Int> = + configurationRepository.onAnyConfigurationChange + .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) } + .onStart { + emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)) + } + private val useAodIconVariant: Flow<Boolean> = + combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) { + isTransitionToAod, + isUdfps -> + isTransitionToAod && isUdfps + } + .distinctUntilChanged() + private val padding: Flow<Int> = + configurationRepository.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } + + val viewModel: Flow<ForegroundIconViewModel> = + combine( + deviceEntryIconViewModel.iconType, + useAodIconVariant, + color, + padding, + ) { iconType, useAodVariant, color, padding -> + ForegroundIconViewModel( + type = iconType, + useAodVariant = useAodVariant, + tint = color, + padding = padding, + ) + } + + data class ForegroundIconViewModel( + val type: DeviceEntryIconView.IconType, + val useAodVariant: Boolean, + val tint: Int, + val padding: Int, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 842dde352c71..5b5a10380a5b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -12,57 +12,202 @@ * 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 android.graphics.Color +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.keyguard.KeyguardViewController +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +/** Models the UI state for the containing device entry icon & long-press handling view. */ @ExperimentalCoroutinesApi -class DeviceEntryIconViewModel @Inject constructor() { - // TODO: b/305234447 update these states from the data layer - val iconViewModel: Flow<IconViewModel> = - flowOf( - IconViewModel( - type = DeviceEntryIconView.IconType.LOCK, - useAodVariant = false, - tint = Color.WHITE, - alpha = 1f, - padding = 48, +class DeviceEntryIconViewModel +@Inject +constructor( + transitions: Set<@JvmSuppressWildcards DeviceEntryIconTransition>, + burnInInteractor: BurnInInteractor, + shadeInteractor: ShadeInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + transitionInteractor: KeyguardTransitionInteractor, + val keyguardInteractor: KeyguardInteractor, + val viewModel: AodToLockscreenTransitionViewModel, + val shadeDependentFlows: ShadeDependentFlows, + private val sceneContainerFlags: SceneContainerFlags, + private val keyguardViewController: Lazy<KeyguardViewController>, + private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, + udfpsInteractor: DeviceEntryUdfpsInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + private val toAodFromState: Flow<KeyguardState> = + transitionInteractor.transitionStepsToState(KeyguardState.AOD).map { it.from } + private val showingAlternateBouncer: Flow<Boolean> = + transitionInteractor.startedKeyguardState.map { keyguardState -> + keyguardState == KeyguardState.ALTERNATE_BOUNCER + } + private val qsProgress: Flow<Float> = shadeInteractor.qsExpansion.onStart { emit(0f) } + private val shadeExpansion: Flow<Float> = shadeInteractor.shadeExpansion.onStart { emit(0f) } + private val transitionAlpha: Flow<Float> = + transitions.map { it.deviceEntryParentViewAlpha }.merge() + private val alphaMultiplierFromShadeExpansion: Flow<Float> = + combine( + showingAlternateBouncer, + shadeExpansion, + qsProgress, + ) { showingAltBouncer, shadeExpansion, qsProgress -> + val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f) + if (showingAltBouncer) { + 1f + } else { + (1f - shadeExpansion) * (1f - interpolatedQsProgress) + } + } + // Burn-in offsets in AOD + private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + burnInInteractor.deviceEntryIconXOffset, + burnInInteractor.deviceEntryIconYOffset, + burnInInteractor.udfpsProgress + ) { fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + fullyDozingBurnInX, + fullyDozingBurnInY, + fullyDozingBurnInProgress, + ) + } + // Burn-in offsets that animate based on the transition amount to AOD + private val animatedBurnInOffsets: Flow<BurnInOffsets> = + combine( + nonAnimatedBurnInOffsets, + transitionInteractor.transitionStepsToState(KeyguardState.AOD) + ) { burnInOffsets, transitionStepsToAod -> + val dozeAmount = transitionStepsToAod.value + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.x), + intEvaluator.evaluate(dozeAmount, 0, burnInOffsets.y), + floatEvaluator.evaluate(dozeAmount, 0, burnInOffsets.progress) ) - ) - val backgroundViewModel: Flow<BackgroundViewModel> = - flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY)) - val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f)) - val isLongPressEnabled: Flow<Boolean> = flowOf(true) + } + + val deviceEntryViewAlpha: Flow<Float> = + combine( + transitionAlpha, + alphaMultiplierFromShadeExpansion, + ) { alpha, alphaMultiplier -> + alpha * alphaMultiplier + } + val useBackgroundProtection: Flow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported + val burnInOffsets: Flow<BurnInOffsets> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + toAodFromState.flatMapLatest { fromState -> + when (fromState) { + KeyguardState.AOD, + KeyguardState.GONE, + KeyguardState.OCCLUDED, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.PRIMARY_BOUNCER -> nonAnimatedBurnInOffsets + KeyguardState.ALTERNATE_BOUNCER -> animatedBurnInOffsets + KeyguardState.LOCKSCREEN -> + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = nonAnimatedBurnInOffsets, + flowWhenShadeIsNotExpanded = animatedBurnInOffsets, + ) + } + } + } else { + // If UDFPS isn't enrolled, we don't show any UI on AOD so there's no need + // to use burn in offsets at all + flowOf(BurnInOffsets(x = 0, y = 0, progress = 0f)) + } + } + val iconType: Flow<DeviceEntryIconView.IconType> = + combine( + udfpsInteractor.isListeningForUdfps, + deviceEntryInteractor.isUnlocked, + ) { isListeningForUdfps, isUnlocked -> + if (isUnlocked) { + DeviceEntryIconView.IconType.UNLOCK + } else { + if (isListeningForUdfps) { + DeviceEntryIconView.IconType.FINGERPRINT + } else { + DeviceEntryIconView.IconType.LOCK + } + } + } + val isLongPressEnabled: Flow<Boolean> = + combine( + iconType, + deviceEntryUdfpsInteractor.isUdfpsSupported, + ) { deviceEntryStatus, isUdfps -> + when (deviceEntryStatus) { + DeviceEntryIconView.IconType.LOCK -> isUdfps + DeviceEntryIconView.IconType.UNLOCK -> true + DeviceEntryIconView.IconType.FINGERPRINT -> false + } + } val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = - flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) + combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled -> + if (longPressEnabled) { + deviceEntryStatus.toAccessibilityHintType() + } else { + DeviceEntryIconView.AccessibilityHintType.NONE + } + } fun onLongPress() { - // TODO() vibrate & perform action based on current lock/unlock state - } - data class BurnInViewModel( - val x: Int, // current x burn in offset based on the aodTransitionAmount - val y: Int, // current y burn in offset based on the aodTransitionAmount - val progress: Float, // current progress based on the aodTransitionAmount - ) + deviceEntryHapticsInteractor.vibrateSuccess() - class IconViewModel( - val type: DeviceEntryIconView.IconType, - val useAodVariant: Boolean, - val tint: Int, - val alpha: Float, - val padding: Int, - ) + // TODO (b/309804148): play auth ripple via an interactor - class BackgroundViewModel( - val alpha: Float, - val tint: Int, - ) + if (sceneContainerFlags.isEnabled()) { + deviceEntryInteractor.attemptDeviceEntry() + } else { + keyguardViewController.get().showPrimaryBouncer(/* scrim */ true) + } + } + + private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): + DeviceEntryIconView.AccessibilityHintType { + return when (this) { + DeviceEntryIconView.IconType.LOCK -> + DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE + DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER + DeviceEntryIconView.IconType.FINGERPRINT -> + DeviceEntryIconView.AccessibilityHintType.NONE + } + } } + +data class BurnInOffsets( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..27fb8a3d2473 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -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.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down DOZING->LOCKSCREEN transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DozingToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation: KeyguardTransitionAnimationFlow = + KeyguardTransitionAnimationFlow( + transitionDuration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = interactor.dozingToLockscreenTransition, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index e24d326850e0..a3b8b85fc53d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -18,27 +18,34 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class DreamingToLockscreenTransitionViewModel @Inject constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, - private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor -) { + private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, + private val deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition() private val transitionAnimation = @@ -88,4 +95,15 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + // immediately show; will fade in with deviceEntryParentViewAlpha + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + override val deviceEntryParentViewAlpha = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 601dbccb1de1..62b2281ae473 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -18,20 +18,27 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class GoneToAodTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( @@ -60,4 +67,21 @@ constructor( onStart = { 0f }, onStep = { it }, ) + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolled -> + if (udfpsEnrolled) { + // fade in at the end of the transition to give time for FP to start running + // and avoid a flicker of the unlocked icon + transitionAnimation.createFlow( + startTime = 1100.milliseconds, + duration = 200.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt deleted file mode 100644 index dd3967aed99d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardAmbientIndicationViewModel.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map -import javax.inject.Inject - -class KeyguardAmbientIndicationViewModel -@Inject -constructor( - private val keyguardInteractor: KeyguardInteractor, - private val burnInHelperWrapper: BurnInHelperWrapper, -) { - - /** An observable for the x-offset by which the indication area should be translated. */ - val indicationAreaTranslationX: Flow<Float> = - keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() - - /** Returns an observable for the y-offset by which the indication area should be translated. */ - fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt new file mode 100644 index 000000000000..2bf12e8e33b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -0,0 +1,85 @@ +/* + * 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down LOCKSCREEN->AOD transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.lockscreenToAodTransition, + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { 1 - it }, + onFinish = { 0f }, + ), + ) + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = // fade in + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ), + flowWhenShadeIsNotExpanded = transitionAnimation.immediatelyTransitionTo(1f), + ) + } else { + shadeDependentFlows.transitionFlow( + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + flowWhenShadeIsNotExpanded = // fade out + transitionAnimation.createFlow( + duration = 200.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f }, + ), + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index a3ae67d906bd..52296137a3d6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -34,7 +35,8 @@ class LockscreenToDreamingTransitionViewModel @Inject constructor( interactor: KeyguardTransitionInteractor, -) { + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_DREAMING_DURATION, @@ -60,6 +62,12 @@ constructor( onStep = { 1f - it }, ) + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) + companion object { @JvmField val DREAMING_ANIMATION_DURATION_MS = TO_DREAMING_DURATION.inWholeMilliseconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt new file mode 100644 index 000000000000..59e5aa845051 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -0,0 +1,48 @@ +/* + * 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.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->GONE transition into discrete steps for corresponding views to consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToGoneTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, + transitionFlow = interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.GONE), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index d3ea89ce1935..d49bc4994b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -33,8 +34,9 @@ import kotlinx.coroutines.flow.Flow class LockscreenToOccludedTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_OCCLUDED_DURATION, @@ -59,4 +61,10 @@ constructor( interpolator = EMPHASIZED_ACCELERATE, ) } + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = lockscreenAlpha, + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f), + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt new file mode 100644 index 000000000000..f04b67a1d4d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -0,0 +1,59 @@ +/* + * 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.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class LockscreenToPrimaryBouncerTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + shadeDependentFlows: ShadeDependentFlows, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + transitionFlow = + interactor.transition(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER), + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + shadeDependentFlows.transitionFlow( + flowWhenShadeIsNotExpanded = + transitionAnimation.createFlow( + duration = 250.milliseconds, + onStep = { 1f - it }, + onFinish = { 0f } + ), + flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f) + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt new file mode 100644 index 000000000000..f7cff9b28542 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -0,0 +1,59 @@ +/* + * 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** Breaks down OCCLUDED->AOD transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class OccludedToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromOccludedTransitionInteractor.TO_AOD_DURATION, + transitionFlow = interactor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled + -> + if (udfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index 6845c55b8385..0bdc85d05106 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -18,23 +18,30 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest /** * Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to * consume. */ +@ExperimentalCoroutinesApi @SysUISingleton class OccludedToLockscreenTransitionViewModel @Inject constructor( - private val interactor: KeyguardTransitionInteractor, -) { + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor +) : DeviceEntryIconTransition { private val transitionAnimation = KeyguardTransitionAnimationFlow( transitionDuration = TO_LOCKSCREEN_DURATION, @@ -58,4 +65,16 @@ constructor( duration = 250.milliseconds, onStep = { it }, ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt new file mode 100644 index 000000000000..05a6d5810be3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -0,0 +1,74 @@ +/* + * 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.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->AOD transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToAodTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.immediatelyTransitionTo(0f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { + isUdfpsEnrolledAndEnabled -> + if (isUdfpsEnrolledAndEnabled) { + transitionAnimation.createFlow( + duration = 300.milliseconds, + onStep = { it }, + onFinish = { 1f }, + ) + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..3cf793ab9dc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest + +/** + * Breaks down PRIMARY BOUNCER->LOCKSCREEN transition into discrete steps for corresponding views to + * consume. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class PrimaryBouncerToLockscreenTransitionViewModel +@Inject +constructor( + interactor: KeyguardTransitionInteractor, + deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, +) : DeviceEntryIconTransition { + private val transitionAnimation = + KeyguardTransitionAnimationFlow( + transitionDuration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + transitionFlow = + interactor.transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.LOCKSCREEN), + ) + + val deviceEntryBackgroundViewAlpha: Flow<Float> = + deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfps -> + if (isUdfps) { + transitionAnimation.immediatelyTransitionTo(1f) + } else { + emptyFlow() + } + } + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt new file mode 100644 index 000000000000..e45d537155fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ShadeDependentFlows.kt @@ -0,0 +1,59 @@ +/* + * 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.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** Helper for flows that depend on the shade expansion */ +class ShadeDependentFlows +@Inject +constructor( + transitionInteractor: KeyguardTransitionInteractor, + shadeInteractor: ShadeInteractor, +) { + /** When the last keyguard state transition started, was the shade fully expanded? */ + private val lastStartedTransitionHadShadeFullyExpanded: Flow<Boolean> = + transitionInteractor.startedKeyguardState.sample(shadeInteractor.isAnyFullyExpanded) + + /** + * Decide which flow to use depending on the shade expansion state at the start of the last + * keyguard state transition. + */ + fun <T> transitionFlow( + flowWhenShadeIsExpanded: Flow<T>, + flowWhenShadeIsNotExpanded: Flow<T>, + ): Flow<T> { + val filteredFlowWhenShadeIsExpanded = + flowWhenShadeIsExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> shadeFullyExpanded } + .map { (valueWhenShadeIsExpanded, _) -> valueWhenShadeIsExpanded } + val filteredFlowWhenShadeIsNotExpanded = + flowWhenShadeIsNotExpanded + .sample(lastStartedTransitionHadShadeFullyExpanded, ::Pair) + .filter { (_, shadeFullyExpanded) -> !shadeFullyExpanded } + .map { (valueWhenShadeIsNotExpanded, _) -> valueWhenShadeIsNotExpanded } + return merge(filteredFlowWhenShadeIsExpanded, filteredFlowWhenShadeIsNotExpanded) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt index c10a4635644f..6e77e13e8513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context -import com.android.systemui.res.R -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.res.R import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,7 +35,7 @@ constructor( val context: Context, ) { val alpha: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets val isVisible: Flow<Boolean> = alpha.map { it != 0f } // Padding between the fingerprint icon and its bounding box in pixels. 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 0b1079f69d6e..642904df21b7 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 @@ -19,9 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.content.Context import androidx.annotation.ColorInt import com.android.settingslib.Utils.getColorAttrDefaultColor -import com.android.systemui.keyguard.domain.interactor.BurnInOffsets import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.Offsets import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState @@ -185,7 +185,7 @@ constructor( keyguardInteractor, ) { val dozeAmount: Flow<Float> = interactor.dozeAmount - val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val burnInOffsets: Flow<Offsets> = interactor.burnInOffsets // Padding between the fingerprint icon and its bounding box in pixels. val padding: Flow<Int> = diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index b1ff708d020b..9d6e9b4f3ed5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -46,8 +46,7 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.shade.ShadeStateEvents -import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -103,7 +102,7 @@ constructor( private val communalInteractor: CommunalInteractor, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, - panelEventsEvents: ShadeStateEvents, + shadeInteractor: ShadeInteractor, private val secureSettings: SecureSettings, @Main private val handler: Handler, @Application private val coroutineScope: CoroutineScope, @@ -545,14 +544,12 @@ constructor( mediaHosts.forEach { it?.updateViewVisibility() } } - panelEventsEvents.addShadeStateEventsListener( - object : ShadeStateEventsListener { - override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { - skipQqsOnExpansion = isExpandImmediateEnabled - updateDesiredLocation() - } + coroutineScope.launch { + shadeInteractor.isQsBypassingShade.collect { isExpandImmediateEnabled -> + skipQqsOnExpansion = isExpandImmediateEnabled + updateDesiredLocation() } - ) + } val settingsObserver: ContentObserver = object : ContentObserver(handler) { diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt index 8453af19d26f..0f54e934f3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt @@ -80,12 +80,13 @@ class MediaProjectionPermissionDialogDelegate( R.string.media_projection_entry_app_permission_dialog_warning_entire_screen } + val singleAppOptionDisabled = + appName != null && + mediaProjectionConfig?.regionToCapture == + MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY + val singleAppDisabledText = - if ( - appName != null && - mediaProjectionConfig?.regionToCapture == - MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY - ) { + if (singleAppOptionDisabled) { context.getString( R.string.media_projection_entry_app_permission_dialog_single_app_disabled, appName @@ -93,19 +94,26 @@ class MediaProjectionPermissionDialogDelegate( } else { null } - return listOf( - ScreenShareOption( - mode = SINGLE_APP, - spinnerText = R.string.screen_share_permission_dialog_option_single_app, - warningText = singleAppWarningText, - spinnerDisabledText = singleAppDisabledText, - ), - ScreenShareOption( - mode = ENTIRE_SCREEN, - spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, - warningText = entireScreenWarningText + val options = + listOf( + ScreenShareOption( + mode = SINGLE_APP, + spinnerText = R.string.screen_share_permission_dialog_option_single_app, + warningText = singleAppWarningText, + spinnerDisabledText = singleAppDisabledText, + ), + ScreenShareOption( + mode = ENTIRE_SCREEN, + spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, + warningText = entireScreenWarningText + ) ) - ) + return if (singleAppOptionDisabled) { + // Make sure "Entire screen" is the first option when "Single App" is disabled. + options.reversed() + } else { + options + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 79aedffc4498..9f5e1b79765e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -35,6 +35,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindowManager; import android.view.View; @@ -94,6 +95,9 @@ public class NavigationBarControllerImpl implements @VisibleForTesting SparseArray<NavigationBar> mNavigationBars = new SparseArray<>(); + /** Local cache for {@link IWindowManager#hasNavigationBar(int)}. */ + private SparseBooleanArray mHasNavBar = new SparseBooleanArray(); + // Tracks config changes that will actually recreate the nav bar private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE @@ -221,10 +225,16 @@ public class NavigationBarControllerImpl implements } private boolean shouldCreateNavBarAndTaskBar(int displayId) { + if (mHasNavBar.indexOfKey(displayId) > -1) { + return mHasNavBar.get(displayId); + } + final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); try { - return wms.hasNavigationBar(displayId); + boolean hasNavigationBar = wms.hasNavigationBar(displayId); + mHasNavBar.put(displayId, hasNavigationBar); + return hasNavigationBar; } catch (RemoteException e) { // Cannot get wms, just return false with warning message. Log.w(TAG, "Cannot get WindowManager."); diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index dbd62fe16309..d9e3e55c1ad3 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -27,10 +27,10 @@ import com.android.systemui.power.shared.model.ScreenPowerState import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import javax.inject.Inject /** Hosts business logic for interacting with the power system. */ @SysUISingleton @@ -59,18 +59,28 @@ constructor( * Whether we're awake (screen is on and responding to user touch) or asleep (screen is off, or * on AOD). */ - val isAwake = repository.wakefulness + val isAwake = + repository.wakefulness .map { it.isAwake() } .distinctUntilChanged(checkEquivalentUnlessEmitDuplicatesUnderTest) - /** - * Helper flow in case "isAsleep" reads better than "!isAwake". - */ + /** Helper flow in case "isAsleep" reads better than "!isAwake". */ val isAsleep = isAwake.map { !it } val screenPowerState = repository.screenPowerState /** + * Notifies the power interactor that a user touch happened. + * + * @param noChangeLights If true, does not cause the keyboard backlight to turn on because of + * this event. This is set when the power key is pressed. We want the device to stay on while + * the button is down, but we're about to turn off the screen so we don't want the keyboard + * backlight to turn on again. Otherwise the lights flash on and then off and it looks weird. + */ + fun onUserTouch(noChangeLights: Boolean = false) = + repository.userTouch(noChangeLights = noChangeLights) + + /** * Wakes up the device if the device was dozing. * * @param why a string explaining why we're waking the device for debugging purposes. Should be @@ -92,9 +102,7 @@ constructor( */ fun wakeUpForFullScreenIntent() { if (repository.wakefulness.value.isAsleep() || statusBarStateController.isDozing) { - repository.wakeUp( - why = FSI_WAKE_WHY, - wakeReason = PowerManager.WAKE_REASON_APPLICATION) + repository.wakeUp(why = FSI_WAKE_WHY, wakeReason = PowerManager.WAKE_REASON_APPLICATION) } } @@ -120,8 +128,8 @@ constructor( * directly. */ fun onStartedWakingUp( - @PowerManager.WakeReason reason: Int, - powerButtonLaunchGestureTriggeredOnWakeUp: Boolean, + @PowerManager.WakeReason reason: Int, + powerButtonLaunchGestureTriggeredOnWakeUp: Boolean, ) { // If the launch gesture was previously detected, either via onCameraLaunchGestureDetected // or onFinishedGoingToSleep(), carry that state forward. It will be reset by the next @@ -210,14 +218,14 @@ constructor( * reset that flag and then return false. */ private val checkEquivalentUnlessEmitDuplicatesUnderTest: (Boolean, Boolean) -> Boolean = - { old, new -> - if (emitDuplicateWakefulnessValue) { - emitDuplicateWakefulnessValue = false - false - } else { - old == new - } + { old, new -> + if (emitDuplicateWakefulnessValue) { + emitDuplicateWakefulnessValue = false + false + } else { + old == new } + } /** * Helper method for tests to simulate the device waking up. @@ -232,14 +240,14 @@ constructor( */ @JvmOverloads fun PowerInteractor.setAwakeForTest( - @PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN, - forceEmit: Boolean = false + @PowerManager.WakeReason reason: Int = PowerManager.WAKE_REASON_UNKNOWN, + forceEmit: Boolean = false ) { emitDuplicateWakefulnessValue = forceEmit this.onStartedWakingUp( - reason = reason, - powerButtonLaunchGestureTriggeredOnWakeUp = false, + reason = reason, + powerButtonLaunchGestureTriggeredOnWakeUp = false, ) this.onFinishedWakingUp() } @@ -258,15 +266,14 @@ constructor( */ @JvmOverloads fun PowerInteractor.setAsleepForTest( - @PowerManager.GoToSleepReason sleepReason: Int = - PowerManager.GO_TO_SLEEP_REASON_MIN, - forceEmit: Boolean = false, + @PowerManager.GoToSleepReason sleepReason: Int = PowerManager.GO_TO_SLEEP_REASON_MIN, + forceEmit: Boolean = false, ) { emitDuplicateWakefulnessValue = forceEmit this.onStartedGoingToSleep(reason = sleepReason) this.onFinishedGoingToSleep( - powerButtonLaunchGestureTriggeredDuringSleep = false, + powerButtonLaunchGestureTriggeredDuringSleep = false, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index b0f54ee78482..064423768cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -28,8 +28,8 @@ import android.view.View; import android.widget.FrameLayout; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.qs.customize.QSCustomizer; +import com.android.systemui.res.R; import com.android.systemui.shade.TouchLogger; import com.android.systemui.util.LargeScreenUtils; @@ -59,6 +59,8 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private boolean mClippingEnabled; private boolean mIsFullWidth; + private boolean mSceneContainerEnabled; + public QSContainerImpl(Context context, AttributeSet attrs) { super(context, attrs); } @@ -72,6 +74,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + } + @Override public boolean hasOverlappingRendering() { return false; @@ -161,7 +167,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } mQSPanelContainer.setPaddingRelative( mQSPanelContainer.getPaddingStart(), - topPadding, + mSceneContainerEnabled ? 0 : topPadding, mQSPanelContainer.getPaddingEnd(), mQSPanelContainer.getPaddingBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java index 73a5faabda3b..7b001c7b72f7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java @@ -24,6 +24,7 @@ import android.view.View; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.ViewController; @@ -44,6 +45,7 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { mView.updateResources(mQsPanelController, mQuickStatusBarHeaderController); } }; + private final boolean mSceneContainerEnabled; private final View.OnTouchListener mContainerTouchHandler = new View.OnTouchListener() { @Override @@ -64,18 +66,21 @@ public class QSContainerImplController extends ViewController<QSContainerImpl> { QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController, ConfigurationController configurationController, - FalsingManager falsingManager) { + FalsingManager falsingManager, + SceneContainerFlags sceneContainerFlags) { super(view); mQsPanelController = qsPanelController; mQuickStatusBarHeaderController = quickStatusBarHeaderController; mConfigurationController = configurationController; mFalsingManager = falsingManager; mQSPanelContainer = mView.getQSPanelContainer(); + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } @Override public void onInit() { mQuickStatusBarHeaderController.init(); + mView.setSceneContainerEnabled(mSceneContainerEnabled); } public void setListening(boolean listening) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java index fab7e952a874..7f91fd2ebb80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java @@ -97,6 +97,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl private boolean mStackScrollerOverscrolling; private QSAnimator mQSAnimator; + @Nullable private HeightListener mPanelView; private QSSquishinessController mQSSquishinessController; protected QuickStatusBarHeader mHeader; @@ -340,6 +341,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } if (mQSCustomizerController != null) { mQSCustomizerController.setQs(null); + mQSCustomizerController.setContainerController(null); } mScrollListener = null; if (mContainer != null) { @@ -347,6 +349,10 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } mDumpManager.unregisterDumpable(getClass().getSimpleName()); mListeningAndVisibilityLifecycleOwner.destroy(); + ViewGroup parent = ((ViewGroup) getView().getParent()); + if (parent != null) { + parent.removeView(getView()); + } } public void onSaveInstanceState(Bundle outState) { @@ -853,6 +859,10 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mQSCustomizerController.hide(); } + public void closeCustomizerImmediately() { + mQSCustomizerController.hide(false); + } + public void notifyCustomizeChanged() { // The customize state changed, so our height changed. mContainer.updateExpansion(); @@ -863,7 +873,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. - mPanelView.onQsHeightChanged(); + if (mPanelView != null) { + mPanelView.onQsHeightChanged(); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 3227f75ed067..ddd7d6781c46 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -39,9 +39,9 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.widget.RemeasuringLinearLayout; -import com.android.systemui.res.R; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.res.R; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; @@ -110,6 +110,8 @@ public class QSPanel extends LinearLayout implements Tunable { */ private boolean mCanCollapse = true; + private boolean mSceneContainerEnabled; + public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); mUsingMediaPlayer = useQsMediaPlayer(context); @@ -153,6 +155,13 @@ public class QSPanel extends LinearLayout implements Tunable { } } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + if (mSceneContainerEnabled) { + updatePadding(); + } + } + protected void setHorizontalContentContainerClipping() { mHorizontalContentContainer.setClipChildren(true); mHorizontalContentContainer.setClipToPadding(false); @@ -375,7 +384,7 @@ public class QSPanel extends LinearLayout implements Tunable { int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); setPaddingRelative(getPaddingStart(), - paddingTop, + mSceneContainerEnabled ? 0 : paddingTop, getPaddingEnd(), paddingBottom); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 6bbdc54d260d..5eb9620d7334 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -34,6 +34,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.brightness.BrightnessController; import com.android.systemui.settings.brightness.BrightnessMirrorHandler; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -61,6 +62,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private boolean mListening; + private final boolean mSceneContainerEnabled; + private View.OnTouchListener mTileLayoutTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -82,7 +85,8 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { BrightnessSliderController.Factory brightnessSliderFactory, FalsingManager falsingManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, - SplitShadeStateController splitShadeStateController) { + SplitShadeStateController splitShadeStateController, + SceneContainerFlags sceneContainerFlags) { super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController); mTunerService = tunerService; @@ -96,6 +100,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } @Override @@ -116,6 +121,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> { mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS); mView.updateResources(); + mView.setSceneContainerEnabled(mSceneContainerEnabled); if (mView.isListening()) { refreshAllTiles(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 67c42086364c..c657b55d42d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -40,6 +40,8 @@ public class QuickStatusBarHeader extends FrameLayout { protected QuickQSPanel mHeaderQsPanel; + private boolean mSceneContainerEnabled; + public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); } @@ -52,6 +54,13 @@ public class QuickStatusBarHeader extends FrameLayout { updateResources(); } + void setSceneContainerEnabled(boolean enabled) { + mSceneContainerEnabled = enabled; + if (mSceneContainerEnabled) { + updateResources(); + } + } + @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -82,7 +91,9 @@ public class QuickStatusBarHeader extends FrameLayout { setLayoutParams(lp); MarginLayoutParams qqsLP = (MarginLayoutParams) mHeaderQsPanel.getLayoutParams(); - if (largeScreenHeaderActive) { + if (mSceneContainerEnabled) { + qqsLP.topMargin = 0; + } else if (largeScreenHeaderActive) { qqsLP.topMargin = mContext.getResources() .getDimensionPixelSize(R.dimen.qqs_layout_margin_top); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index 64960e6ce23e..1d92d782be69 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -17,6 +17,7 @@ package com.android.systemui.qs; import com.android.systemui.qs.dagger.QSScope; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.util.ViewController; import javax.inject.Inject; @@ -29,17 +30,20 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader private final QuickQSPanelController mQuickQSPanelController; private boolean mListening; + private final boolean mSceneContainerEnabled; @Inject QuickStatusBarHeaderController(QuickStatusBarHeader view, - QuickQSPanelController quickQSPanelController + QuickQSPanelController quickQSPanelController, + SceneContainerFlags sceneContainerFlags ) { super(view); mQuickQSPanelController = quickQSPanelController; + mSceneContainerEnabled = sceneContainerFlags.isEnabled(); } - @Override protected void onViewAttached() { + mView.setSceneContainerEnabled(mSceneContainerEnabled); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java index 024e760e6ed1..c28371ce8aaf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java @@ -244,7 +244,12 @@ public class QSCustomizerController extends ViewController<QSCustomizer> { /** Hice the customizer. */ public void hide() { - final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF; + hide(true); + } + + public void hide(boolean animated) { + final boolean animate = animated + && mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF; if (mView.isShown()) { mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED); mToolbar.dismissPopupMenus(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index bd4c6e1930ba..1aef9206d99f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -32,6 +32,8 @@ import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.pipeline.dagger.QSPipelineModule; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.di.QSTilesModule; +import com.android.systemui.qs.ui.adapter.QSSceneAdapter; +import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.policy.CastController; @@ -42,18 +44,19 @@ import com.android.systemui.statusbar.policy.SafetyController; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.util.settings.SecureSettings; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.Multibinds; - import java.util.Map; import javax.inject.Named; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.Multibinds; + /** * Module for QS dependencies */ -@Module(subcomponents = {QSFragmentComponent.class, QSFlexiglassComponent.class}, +@Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class}, includes = { MediaModule.class, QSExternalModule.class, @@ -110,4 +113,7 @@ public interface QSModule { manager.init(); return manager; } + + @Binds + QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt index ba1aa629f8cd..b9423683446c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneComponent.kt @@ -21,12 +21,12 @@ import com.android.systemui.dagger.qualifiers.RootView import dagger.BindsInstance import dagger.Subcomponent -@Subcomponent(modules = [QSFlexiglassModule::class]) +@Subcomponent(modules = [QSSceneModule::class]) @QSScope -interface QSFlexiglassComponent : QSComponent { +interface QSSceneComponent : QSComponent { @Subcomponent.Factory interface Factory { - fun create(@RootView @BindsInstance rootView: View): QSFlexiglassComponent + fun create(@RootView @BindsInstance rootView: View): QSSceneComponent } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt index 36fac44e0173..446cb62464fc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlexiglassModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSSceneModule.kt @@ -24,7 +24,7 @@ import dagger.Provides import javax.inject.Named @Module(includes = [QSScopeModule::class]) -interface QSFlexiglassModule { +interface QSSceneModule { @Module companion object { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt index 736f7cfbfce9..ae554d954580 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -26,6 +26,7 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.logging.QSTileLogger import com.android.systemui.qs.tiles.impl.di.QSTileComponent +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.user.data.repository.UserRepository @@ -60,12 +61,17 @@ sealed interface QSTileViewModelFactory<T> { ) : QSTileViewModelFactory<T> { /** - * Creates [QSTileViewModelImpl] based on the interactors obtained from [component]. - * Reference of that [component] is then stored along the view model. + * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent]. + * Reference of that [QSTileComponent] is then stored along the view model. */ - fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> = - QSTileViewModelImpl( - qsTileConfigProvider.getConfig(tileSpec.spec), + fun create( + tileSpec: TileSpec, + componentFactory: (config: QSTileConfig) -> QSTileComponent<T> + ): QSTileViewModelImpl<T> { + val config = qsTileConfigProvider.getConfig(tileSpec.spec) + val component = componentFactory(config) + return QSTileViewModelImpl( + config, component::userActionInteractor, component::dataInteractor, component::dataToStateMapper, @@ -77,6 +83,7 @@ sealed interface QSTileViewModelFactory<T> { systemClock, backgroundDispatcher, ) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 12a083e990f8..5e19439cd643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -116,7 +116,7 @@ class QSTileViewModelImpl<DATA_TYPE>( ) override fun forceUpdate() { - forceUpdates.tryEmit(Unit) + tileScope.launch { forceUpdates.emit(Unit) } } override fun onUserChanged(user: UserHandle) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 7d7af64a3038..27007bbf7aee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -19,6 +19,11 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent +import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter @@ -34,18 +39,31 @@ constructor( private val adapterFactory: QSTileViewModelAdapter.Factory, private val tileMap: Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>, + private val customTileComponentBuilder: CustomTileComponent.Builder, + private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>, ) : QSFactory { init { for (viewModelTileSpec in tileMap.keys) { - // throws an exception when there is no config for a tileSpec of an injected viewModel - qsTileConfigProvider.getConfig(viewModelTileSpec) + require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) { + "No config for $viewModelTileSpec" + } } } - override fun createTile(tileSpec: String): QSTile? = - tileMap[tileSpec]?.let { - val tile = it.get() - adapterFactory.create(tile) + override fun createTile(tileSpec: String): QSTile? { + val viewModel: QSTileViewModel = + when (val spec = TileSpec.create(tileSpec)) { + is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec) + is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get() + is TileSpec.Invalid -> null + } + ?: return null + return adapterFactory.create(viewModel) + } + + private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel = + customTileViewModelFactory.create(spec) { config -> + customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 7b4b55702f55..5bdb592a3558 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -20,9 +20,12 @@ import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View +import android.view.View.AccessibilityDelegate import android.view.View.GONE import android.view.View.VISIBLE import android.view.ViewGroup +import android.view.accessibility.AccessibilityNodeInfo +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import android.widget.ImageView import android.widget.Switch import android.widget.TextView @@ -32,13 +35,18 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.withContext /** Dialog for showing active, connected and saved bluetooth devices. */ @SysUISingleton @@ -47,6 +55,7 @@ constructor( private val bluetoothToggleInitialValue: Boolean, private val subtitleResIdInitialValue: Int, private val bluetoothTileDialogCallback: BluetoothTileDialogCallback, + @Main private val mainDispatcher: CoroutineDispatcher, private val systemClock: SystemClock, private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, @@ -65,13 +74,17 @@ constructor( private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback) + private var lastUiUpdateMs: Long = -1 + + private var lastItemRow: Int = -1 + private lateinit var toggleView: Switch private lateinit var subtitleTextView: TextView private lateinit var doneButton: View private lateinit var seeAllViewGroup: View private lateinit var pairNewDeviceViewGroup: View - private lateinit var seeAllText: View - private lateinit var pairNewDeviceText: View + private lateinit var seeAllRow: View + private lateinit var pairNewDeviceRow: View private lateinit var deviceListView: RecyclerView override fun onCreate(savedInstanceState: Bundle?) { @@ -88,8 +101,8 @@ constructor( doneButton = requireViewById(R.id.done_button) seeAllViewGroup = requireViewById(R.id.see_all_layout_group) pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group) - seeAllText = requireViewById(R.id.see_all_text) - pairNewDeviceText = requireViewById(R.id.pair_new_device_text) + seeAllRow = requireViewById(R.id.see_all_clickable_row) + pairNewDeviceRow = requireViewById(R.id.pair_new_device_clickable_row) deviceListView = requireViewById<RecyclerView>(R.id.device_list) setupToggle() @@ -97,22 +110,37 @@ constructor( subtitleTextView.text = context.getString(subtitleResIdInitialValue) doneButton.setOnClickListener { dismiss() } - seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } - pairNewDeviceText.setOnClickListener { + seeAllRow.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) } + pairNewDeviceRow.setOnClickListener { bluetoothTileDialogCallback.onPairNewDeviceClicked(it) } } - internal fun onDeviceItemUpdated( + override fun start() { + lastUiUpdateMs = systemClock.elapsedRealtime() + } + + internal suspend fun onDeviceItemUpdated( deviceItem: List<DeviceItem>, showSeeAll: Boolean, showPairNewDevice: Boolean ) { - val start = systemClock.elapsedRealtime() - deviceItemAdapter.refreshDeviceItemList(deviceItem) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE - logger.logDeviceUiUpdate(systemClock.elapsedRealtime() - start) + withContext(mainDispatcher) { + val start = systemClock.elapsedRealtime() + val itemRow = deviceItem.size + showSeeAll.toInt() + showPairNewDevice.toInt() + // Add a slight delay for smoother dialog height change + if (itemRow != lastItemRow) { + delay(MIN_HEIGHT_CHANGE_INTERVAL_MS - (start - lastUiUpdateMs)) + } + if (isActive) { + deviceItemAdapter.refreshDeviceItemList(deviceItem) { + seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + lastUiUpdateMs = systemClock.elapsedRealtime() + lastItemRow = itemRow + logger.logDeviceUiUpdate(lastUiUpdateMs - start) + } + } } } @@ -169,7 +197,8 @@ constructor( deviceItem1.iconWithDescription?.second == deviceItem2.iconWithDescription?.second && deviceItem1.background == deviceItem2.background && - deviceItem1.isEnabled == deviceItem2.isEnabled + deviceItem1.isEnabled == deviceItem2.isEnabled && + deviceItem1.actionAccessibilityLabel == deviceItem2.actionAccessibilityLabel } } @@ -213,6 +242,21 @@ constructor( mutableDeviceItemClick.tryEmit(item) uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED) } + accessibilityDelegate = + object : AccessibilityDelegate() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfo + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + info.addAction( + AccessibilityAction( + AccessibilityAction.ACTION_CLICK.id, + item.actionAccessibilityLabel + ) + ) + } + } } nameView.text = item.deviceName summaryView.text = item.connectionSummary @@ -230,6 +274,7 @@ constructor( } internal companion object { + const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L const val MAX_DEVICE_ITEM_ENTRY = 3 const val ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS" @@ -238,5 +283,9 @@ constructor( const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f + + private fun Boolean.toInt(): Int { + return if (this) 1 else 0 + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index f7e0de3ed883..34c2aba1a71f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -40,7 +40,6 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -76,6 +75,7 @@ constructor( dismissDialog() var updateDeviceItemJob: Job? = null + var updateDialogUiJob: Job? = null job = coroutineScope.launch(mainDispatcher) { @@ -93,10 +93,9 @@ constructor( ) } ?: dialog!!.show() + updateDeviceItemJob?.cancel() updateDeviceItemJob = launch { - // Add a slight delay for smoother dialog bounds change - delay(FIRST_LOAD_DELAY_MS) deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD) } @@ -128,11 +127,14 @@ constructor( deviceItemInteractor.deviceItemUpdate .onEach { - dialog!!.onDeviceItemUpdated( - it.take(MAX_DEVICE_ITEM_ENTRY), - showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, - showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled - ) + updateDialogUiJob?.cancel() + updateDialogUiJob = launch { + dialog?.onDeviceItemUpdated( + it.take(MAX_DEVICE_ITEM_ENTRY), + showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY, + showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled + ) + } } .launchIn(this) @@ -153,6 +155,7 @@ constructor( bluetoothStateInteractor.isBluetoothEnabled, getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled), this@BluetoothTileDialogViewModel, + mainDispatcher, systemClock, uiEventLogger, logger, @@ -205,7 +208,6 @@ constructor( companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" - private const val FIRST_LOAD_DELAY_MS = 500L private fun getSubtitleResId(isBluetoothEnabled: Boolean) = if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle else R.string.bt_is_off diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt index 2c8d2a0806d2..1c621b87533d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt @@ -49,5 +49,6 @@ data class DeviceItem( val connectionSummary: String = "", val iconWithDescription: Pair<Drawable, String>? = null, val background: Int? = null, - var isEnabled: Boolean = true + var isEnabled: Boolean = true, + var actionAccessibilityLabel: String = "", ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt index 7bb1619c5001..1c9be0f105b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt @@ -28,6 +28,10 @@ private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy private val connected = R.string.quick_settings_bluetooth_device_connected private val saved = R.string.quick_settings_bluetooth_device_saved +private val actionAccessibilityLabelActivate = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate +private val actionAccessibilityLabelDisconnect = + R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */ internal abstract class DeviceItemFactory { @@ -60,6 +64,7 @@ internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() { }, background = backgroundOn, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -87,6 +92,7 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } @@ -112,6 +118,7 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect), ) } } @@ -137,6 +144,7 @@ internal class SavedDeviceItemFactory : DeviceItemFactory() { }, background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff, isEnabled = !cachedDevice.isBusy, + actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt index 761274e96e43..14bf25d10d88 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt @@ -19,17 +19,18 @@ package com.android.systemui.qs.tiles.impl.custom import android.os.UserHandle import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject import kotlinx.coroutines.flow.Flow @QSTileScope -class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> { +class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> { override fun tileData( user: UserHandle, triggers: Flow<DataUpdateTrigger> - ): Flow<CustomTileData> { + ): Flow<CustomTileDataModel> { TODO("Not yet implemented") } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt index f7bec024b7bb..e23a5c2f0b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt @@ -17,15 +17,16 @@ package com.android.systemui.qs.tiles.impl.custom import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import javax.inject.Inject @QSTileScope -class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> { +class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> { - override fun map(config: QSTileConfig, data: CustomTileData): QSTileState { + override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState { TODO("Not yet implemented") } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt index 6c1c1a34abc0..f34704be8bc5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt @@ -18,14 +18,15 @@ package com.android.systemui.qs.tiles.impl.custom import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileScope import javax.inject.Inject @QSTileScope class CustomTileUserActionInteractor @Inject constructor() : - QSTileUserActionInteractor<CustomTileData> { + QSTileUserActionInteractor<CustomTileDataModel> { - override suspend fun handleInput(input: QSTileInput<CustomTileData>) { + override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) { TODO("Not yet implemented") } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt index 01df90662579..88bc8fa81e1a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt @@ -16,13 +16,14 @@ package com.android.systemui.qs.tiles.impl.custom.di +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileComponent import com.android.systemui.qs.tiles.impl.di.QSTileScope import dagger.Subcomponent @QSTileScope @Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class]) -interface CustomTileComponent : QSTileComponent<Any> { +interface CustomTileComponent : QSTileComponent<CustomTileDataModel> { @Subcomponent.Builder interface Builder { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt index 482bf9bcd051..83767aa9d444 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt @@ -19,13 +19,13 @@ package com.android.systemui.qs.tiles.impl.custom.di import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor -import com.android.systemui.qs.tiles.impl.custom.CustomTileData import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import dagger.Binds import dagger.Module @@ -36,15 +36,15 @@ interface CustomTileModule { @Binds fun bindDataInteractor( dataInteractor: CustomTileInteractor - ): QSTileDataInteractor<CustomTileData> + ): QSTileDataInteractor<CustomTileDataModel> @Binds fun bindUserActionInteractor( userActionInteractor: CustomTileUserActionInteractor - ): QSTileUserActionInteractor<CustomTileData> + ): QSTileUserActionInteractor<CustomTileDataModel> @Binds - fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData> + fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileDataModel> @Binds fun bindCustomTileDefaultsRepository( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt index bb5a229a0696..f095c01126c4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.tiles.impl.custom +package com.android.systemui.qs.tiles.impl.custom.domain.entity import android.content.ComponentName import android.graphics.drawable.Icon @@ -22,12 +22,11 @@ import android.os.UserHandle import android.service.quicksettings.Tile import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent -data class CustomTileData( +data class CustomTileDataModel( val user: UserHandle, val componentName: ComponentName, val tile: Tile, val callingAppUid: Int, - val isActive: Boolean, val hasPendingBind: Boolean, val shouldShowChevron: Boolean, val defaultTileLabel: CharSequence?, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt index 3f3b94e65294..0609e797d53b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt @@ -18,20 +18,31 @@ package com.android.systemui.qs.tiles.viewmodel import com.android.internal.util.Preconditions import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject interface QSTileConfigProvider { /** - * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no - * config for such [tileSpec]. + * Returns a [QSTileConfig] for a [tileSpec]: + * - injected config for [TileSpec.PlatformTileSpec] or throws [IllegalArgumentException] if + * there is none + * - new config for [TileSpec.CustomTileSpec]. + * - throws [IllegalArgumentException] for [TileSpec.Invalid] */ fun getConfig(tileSpec: String): QSTileConfig + + fun hasConfig(tileSpec: String): Boolean } @SysUISingleton -class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) : - QSTileConfigProvider { +class QSTileConfigProviderImpl +@Inject +constructor( + private val configs: Map<String, QSTileConfig>, + private val qsEventLogger: QsEventLogger, +) : QSTileConfigProvider { init { for (entry in configs.entries) { @@ -44,6 +55,26 @@ class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<Stri } } + override fun hasConfig(tileSpec: String): Boolean = + when (TileSpec.create(tileSpec)) { + is TileSpec.PlatformTileSpec -> configs.containsKey(tileSpec) + is TileSpec.CustomTileSpec -> true + is TileSpec.Invalid -> false + } + override fun getConfig(tileSpec: String): QSTileConfig = - configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec") + when (val spec = TileSpec.create(tileSpec)) { + is TileSpec.PlatformTileSpec -> { + configs[tileSpec] + ?: throw IllegalArgumentException("There is no config for spec=$tileSpec") + } + is TileSpec.CustomTileSpec -> + QSTileConfig( + spec, + QSTileUIConfig.Empty, + qsEventLogger.getNewInstanceId(), + ) + is TileSpec.Invalid -> + throw IllegalArgumentException("TileSpec.Invalid doesn't support configs") + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt new file mode 100644 index 000000000000..b4340f5d59d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.adapter + +import android.content.Context +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.annotation.VisibleForTesting +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.qs.QSContainerController +import com.android.systemui.qs.QSImpl +import com.android.systemui.qs.dagger.QSSceneComponent +import com.android.systemui.res.R +import com.android.systemui.util.kotlin.sample +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Provider +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +// TODO(307945185) Split View concerns into a ViewBinder +/** Adapter to use between Scene system and [QSImpl] */ +interface QSSceneAdapter { + /** Whether [QSImpl] is currently customizing */ + val isCustomizing: StateFlow<Boolean> + + /** + * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by + * the interactor. + */ + val qsView: Flow<View> + + /** + * Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in + * [qsView] + */ + suspend fun inflate(context: Context, parent: ViewGroup? = null) + + /** Set the current state for QS. [state] must not be [State.INITIAL]. */ + fun setState(state: State) + + sealed class State( + val isVisible: Boolean, + val expansion: Float, + ) { + data object CLOSED : State(false, 0f) + data object QQS : State(true, 0f) + data object QS : State(true, 1f) + } +} + +@SysUISingleton +class QSSceneAdapterImpl +@VisibleForTesting +constructor( + private val qsSceneComponentFactory: QSSceneComponent.Factory, + private val qsImplProvider: Provider<QSImpl>, + @Main private val mainDispatcher: CoroutineDispatcher, + @Application applicationScope: CoroutineScope, + private val asyncLayoutInflaterFactory: (Context) -> AsyncLayoutInflater, +) : QSContainerController, QSSceneAdapter { + + @Inject + constructor( + qsSceneComponentFactory: QSSceneComponent.Factory, + qsImplProvider: Provider<QSImpl>, + @Main dispatcher: CoroutineDispatcher, + @Application scope: CoroutineScope, + ) : this(qsSceneComponentFactory, qsImplProvider, dispatcher, scope, ::AsyncLayoutInflater) + + private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED) + private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isCustomizing = _isCustomizing.asStateFlow() + + private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null) + val qsImpl = _qsImpl.asStateFlow() + override val qsView: Flow<View> = _qsImpl.map { it?.view }.filterNotNull() + + init { + applicationScope.launch { + state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> + _qsImpl.value?.apply { + if (state != QSSceneAdapter.State.QS && customizing) { + this@apply.closeCustomizerImmediately() + } + applyState(state) + } + } + } + } + + override fun setCustomizerAnimating(animating: Boolean) {} + + override fun setCustomizerShowing(showing: Boolean) { + _isCustomizing.value = showing + } + + override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) { + setCustomizerShowing(showing) + } + + override fun setDetailShowing(showing: Boolean) {} + + override suspend fun inflate(context: Context, parent: ViewGroup?) { + withContext(mainDispatcher) { + val inflater = asyncLayoutInflaterFactory(context) + val view = suspendCoroutine { continuation -> + inflater.inflate(R.layout.qs_panel, parent) { view, _, _ -> + continuation.resume(view) + } + } + val bundle = Bundle() + _qsImpl.value?.onSaveInstanceState(bundle) + _qsImpl.value?.onDestroy() + val component = qsSceneComponentFactory.create(view) + val qs = qsImplProvider.get() + qs.onCreate(null) + qs.onComponentCreated(component, bundle) + _qsImpl.value = qs + qs.view.setPadding(0, 0, 0, 0) + qs.setContainerController(this@QSSceneAdapterImpl) + qs.applyState(state.value) + } + } + override fun setState(state: QSSceneAdapter.State) { + this.state.value = state + } + + private fun QSImpl.applyState(state: QSSceneAdapter.State) { + setQsVisible(state.isVisible) + setExpanded(state.isVisible) + setListening(state.isVisible) + setQsExpansion(state.expansion, 1f, 0f, 1f) + setTransitionToFullShadeProgress(false, 1f, 1f) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 5993cf104318..3941fd75ebaa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -18,8 +18,14 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import com.android.systemui.scene.shared.model.Direction +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import javax.inject.Inject +import kotlinx.coroutines.flow.map /** Models UI state and handles user input for the quick settings scene. */ @SysUISingleton @@ -28,7 +34,20 @@ class QuickSettingsSceneViewModel constructor( private val deviceEntryInteractor: DeviceEntryInteractor, val shadeHeaderViewModel: ShadeHeaderViewModel, + val qsSceneAdapter: QSSceneAdapter, ) { /** Notifies that some content in quick settings was clicked. */ fun onContentClicked() = deviceEntryInteractor.attemptDeviceEntry() + + val destinationScenes = + qsSceneAdapter.isCustomizing.map { customizing -> + if (customizing) { + mapOf<UserAction, SceneModel>(UserAction.Back to SceneModel(SceneKey.QuickSettings)) + } else { + mapOf( + UserAction.Back to SceneModel(SceneKey.Shade), + UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index b1440031a2a0..0abde4d5c3f4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.power.data.repository.PowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.ObservableTransitionState @@ -48,7 +48,7 @@ class SceneInteractor constructor( @Application private val applicationScope: CoroutineScope, private val repository: SceneContainerRepository, - private val powerRepository: PowerRepository, + private val powerInteractor: PowerInteractor, private val logger: SceneLogger, ) { @@ -202,7 +202,7 @@ constructor( /** Handles a user input event. */ fun onUserInput() { - powerRepository.userTouch() + powerInteractor.onUserTouch() } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index ca2828b99d95..8def457423e4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -19,7 +19,10 @@ package com.android.systemui.scene.domain.startable import com.android.systemui.CoreStartable +import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton @@ -72,6 +75,8 @@ constructor( private val sceneLogger: SceneLogger, @FalsingCollectorActual private val falsingCollector: FalsingCollector, private val powerInteractor: PowerInteractor, + private val simBouncerInteractor: SimBouncerInteractor, + private val authenticationInteractor: AuthenticationInteractor, ) : CoreStartable { override fun start() { @@ -132,6 +137,33 @@ constructor( } } applicationScope.launch { + simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked -> + val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value + val isUnlocked = deviceEntryInteractor.isUnlocked.value + + when { + isAnySimLocked -> { + switchToScene( + targetSceneKey = SceneKey.Bouncer, + loggingReason = "Need to authenticate locked sim card." + ) + } + isUnlocked && !canSwipeToEnter -> { + switchToScene( + targetSceneKey = SceneKey.Gone, + loggingReason = "Sim cards are unlocked." + ) + } + else -> { + switchToScene( + targetSceneKey = SceneKey.Lockscreen, + loggingReason = "Sim cards are unlocked." + ) + } + } + } + } + applicationScope.launch { deviceEntryInteractor.isUnlocked .mapNotNull { isUnlocked -> val renderedScenes = @@ -206,6 +238,14 @@ constructor( "device is waking up while unlocked without the ability" + " to swipe up on lockscreen to enter.", ) + } else if ( + authenticationInteractor.getAuthenticationMethod() == + AuthenticationMethodModel.Sim + ) { + switchToScene( + targetSceneKey = SceneKey.Bouncer, + loggingReason = "device is starting to wake up with a locked sim" + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index e40d2b7fd659..d14ef35027a3 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -17,8 +17,8 @@ package com.android.systemui.scene.shared.flag import androidx.annotation.VisibleForTesting -import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags as AConfigFlags +import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.Flags.sceneContainer import com.android.systemui.compose.ComposeFacade import com.android.systemui.dagger.SysUISingleton @@ -28,6 +28,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import dagger.Module import dagger.Provides import dagger.assisted.Assisted @@ -58,8 +59,6 @@ constructor( @VisibleForTesting val classicFlagTokens: List<Flag<Boolean>> = listOf( - Flags.MIGRATE_NSSL, - Flags.MIGRATE_KEYGUARD_STATUS_VIEW, Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, ) } @@ -75,6 +74,10 @@ constructor( flagName = AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, flagValue = keyguardBottomAreaRefactor(), ), + AconfigFlagMustBeEnabled( + flagName = KeyguardShadeMigrationNssl.FLAG_NAME, + flagValue = KeyguardShadeMigrationNssl.isEnabled, + ), ) + classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } + listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled()) diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index ead10d6da860..9f4ea27b9ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -181,12 +181,16 @@ public class BrightnessDialog extends Activity { if (mCancelTimeoutRunnable != null) { mCancelTimeoutRunnable.run(); } - finish(); + requestFinish(); } return super.onKeyDown(keyCode, event); } + protected void requestFinish() { + finish(); + } + private boolean triggeredByBrightnessKey() { return getIntent().getBooleanExtra(EXTRA_FROM_BRIGHTNESS_KEY, false); } @@ -197,6 +201,6 @@ public class BrightnessDialog extends Activity { } final int timeout = mAccessibilityMgr.getRecommendedTimeoutMillis(DIALOG_TIMEOUT_MILLIS, AccessibilityManager.FLAG_CONTENT_CONTROLS); - mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::finish, timeout); + mCancelTimeoutRunnable = mMainExecutor.executeDelayed(this::requestFinish, timeout); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index e9c930ae7614..a44b4b4f11fe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -29,7 +29,6 @@ import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.GENERIC; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.UNLOCK; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll; import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; @@ -41,7 +40,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_Q import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; -import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.util.DumpUtilsKt.asIndenting; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -64,7 +62,6 @@ import android.graphics.Region; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; -import android.os.Process; import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; @@ -137,6 +134,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInterac import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; @@ -357,6 +356,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final NotificationGutsManager mGutsManager; private final AlternateBouncerInteractor mAlternateBouncerInteractor; private final QuickSettingsController mQsController; + private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; private final TouchHandler mTouchHandler = new TouchHandler(); private long mDownTime; @@ -409,6 +409,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private float mOverStretchAmount; private float mDownX; private float mDownY; + private boolean mIsTrackpadReverseScroll; private int mDisplayTopInset = 0; // in pixels private int mDisplayRightInset = 0; // in pixels private int mDisplayLeftInset = 0; // in pixels @@ -777,7 +778,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, SplitShadeStateController splitShadeStateController, PowerInteractor powerInteractor, - KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) { + KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm, + NaturalScrollingSettingObserver naturalScrollingSettingObserver) { keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override public void onKeyguardFadingAwayChanged() { @@ -806,6 +808,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mPowerInteractor = powerInteractor; mKeyguardViewConfigurator = keyguardViewConfigurator; mClockPositionAlgorithm = keyguardClockPositionAlgorithm; + mNaturalScrollingSettingObserver = naturalScrollingSettingObserver; mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -1265,7 +1268,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.onDestroy(); } - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // Need a shared controller until mKeyguardStatusViewController can be removed from // here, due to important state being set in that controller. Rebind in order to pick // up config changes @@ -1318,7 +1321,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationStackScrollLayoutController.setOverExpansion(0); mNotificationStackScrollLayoutController.setOverScrollAmount(0); } @@ -1339,7 +1342,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } updateClockAppearance(); mQsController.updateQsState(); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationStackScrollLayoutController.updateFooter(); } } @@ -1371,7 +1374,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void reInflateViews() { debugLog("reInflateViews"); // Re-inflate the status view group. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { KeyguardStatusView keyguardStatusView = mNotificationContainerParent.findViewById(R.id.keyguard_status_view); int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView); @@ -1483,7 +1486,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateMaxDisplayedNotifications(boolean recompute) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return; } @@ -1640,7 +1643,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), mKeyguardStatusViewController.isClockTopAligned()); mClockPositionAlgorithm.run(mClockPositionResult); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } @@ -1654,7 +1657,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockY, mClockPositionResult.clockScale, animateClock); @@ -1727,7 +1730,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); ConstraintLayout layout; - if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { layout = mKeyguardViewConfigurator.getKeyguardRootView(); } else { layout = mNotificationContainerParent; @@ -1902,7 +1905,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha; mKeyguardStatusViewController.setAlpha(alpha); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // TODO (b/296373478) This is for split shade media movement. } else { mKeyguardStatusViewController @@ -2482,7 +2485,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump void requestScrollerTopPaddingUpdate(boolean animate) { float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing, getKeyguardNotificationStaticPadding(), mExpandedFraction); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { mSharedNotificationContainerInteractor.setTopPosition(padding); } else { mNotificationStackScrollLayoutController.updateTopPadding(padding, animate); @@ -2840,16 +2843,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } if (!mStatusBarStateController.isDozing()) { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT); - } else { - mVibratorHelper.vibrate( - Process.myUid(), - mView.getContext().getPackageName(), - ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, - "falsing-additional-tap-required", - TOUCH_VIBRATION_ATTRIBUTES); - } + mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT); } } @@ -2949,7 +2943,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onScreenTurningOn() { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.dozeTimeTick(); } } @@ -3201,7 +3195,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump public void dozeTimeTick() { mLockIconViewController.dozeTimeTick(); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.dozeTimeTick(); } if (mInterpolatedDarkAmount > 0) { @@ -3677,14 +3671,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void maybeVibrateOnOpening(boolean openingWithTouch) { if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) { if (!openingWithTouch || !mHasVibratedOnOpen) { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mVibratorHelper.performHapticFeedback( - mView, - HapticFeedbackConstants.GESTURE_START - ); - } else { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); - } + mVibratorHelper.performHapticFeedback( + mView, + HapticFeedbackConstants.GESTURE_START + ); mHasVibratedOnOpen = true; mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true"); } @@ -3697,7 +3687,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ private boolean isDirectionUpwards(float x, float y) { float xDiff = x - mInitialExpandX; - float yDiff = y - mInitialExpandY; + float yDiff = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY); if (yDiff >= 0) { return false; } @@ -3734,7 +3724,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump || (!isFullyExpanded() && !isFullyCollapsed()) || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { mVelocityTracker.computeCurrentVelocity(1000); - float vel = mVelocityTracker.getYVelocity(); + float vel = (mIsTrackpadReverseScroll ? -1 : 1) * mVelocityTracker.getYVelocity(); float vectorVel = (float) Math.hypot( mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); @@ -3773,8 +3763,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp); mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_UNLOCK); } + float dy = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY); @Classifier.InteractionType int interactionType = vel == 0 ? GENERIC - : y - mInitialExpandY > 0 ? QUICK_SETTINGS + : dy > 0 ? QUICK_SETTINGS : (mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); @@ -3801,7 +3792,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private float getCurrentExpandVelocity() { mVelocityTracker.computeCurrentVelocity(1000); - return mVelocityTracker.getYVelocity(); + return (mIsTrackpadReverseScroll ? -1 : 1) * mVelocityTracker.getYVelocity(); } private void endClosing() { @@ -4427,7 +4418,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump && statusBarState == KEYGUARD) { // This means we're doing the screen off animation - position the keyguard status // view where it'll be on AOD, so we can animate it in. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePosition( mClockPositionResult.clockX, mClockPositionResult.clockYFullyDozing, @@ -4547,7 +4538,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setDozing(true /* dozing */, false /* animate */); mStatusBarStateController.setUpcomingState(KEYGUARD); - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { mStatusBarStateController.setState(KEYGUARD); } else { mStatusBarStateListener.onStateChanged(KEYGUARD); @@ -4608,7 +4599,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); // Update Clock Pivot (used by anti-burnin transformations) - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); } @@ -4718,7 +4709,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private Consumer<Float> setTransitionY( NotificationStackScrollLayoutController stackScroller) { return (Float translationY) -> { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mKeyguardStatusViewController.setTranslationY(translationY, /* excludeMedia= */false); stackScroller.setTranslationY(translationY); @@ -4760,7 +4751,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) { + if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { return false; } @@ -4842,6 +4833,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump + " mAnimatingOnDown: true, mClosing: true"); return true; } + + mIsTrackpadReverseScroll = + !mNaturalScrollingSettingObserver.isNaturalScrollingEnabled() + && isTrackpadScroll(mTrackpadGestureFeaturesEnabled, event); if (!isTracking() || isFullyCollapsed()) { mInitialExpandY = y; mInitialExpandX = x; @@ -4884,7 +4879,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } break; case MotionEvent.ACTION_MOVE: - final float h = y - mInitialExpandY; + final float h = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY); addMovement(event); final boolean openShadeWithoutHun = mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; @@ -4926,7 +4921,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump */ @Override public boolean onTouchEvent(MotionEvent event) { - if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) { + if (KeyguardShadeMigrationNssl.isEnabled() && !mUseExternalTouch) { return false; } @@ -5148,7 +5143,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (!isFullyCollapsed()) { maybeVibrateOnOpening(true /* openingWithTouch */); } - float h = y - mInitialExpandY; + float h = (mIsTrackpadReverseScroll ? -1 : 1) * (y - mInitialExpandY); // If the panel was collapsed when touching, we only need to check for the // y-component of the gesture, as we have no conflicting horizontal gesture. @@ -5197,6 +5192,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mQsController.cancelJankMonitoring(); } } + mIsTrackpadReverseScroll = false; break; } return !mGestureWaitForTouchSlop || isTracking(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index a2ca49d9ba57..d0f2784a98b3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -53,6 +53,7 @@ import com.android.systemui.flags.Flags; import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; @@ -457,7 +458,7 @@ public class NotificationShadeWindowViewController implements Dumpable { && !bouncerShowing && !mStatusBarStateController.isDozing()) { if (mDragDownHelper.isDragDownEnabled()) { - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // When on lockscreen, if the touch originates at the top of the screen // go directly to QS and not the shade if (mQuickSettingsController.shouldQuickSettingsIntercept( @@ -469,7 +470,7 @@ public class NotificationShadeWindowViewController implements Dumpable { // This handles drag down over lockscreen boolean result = mDragDownHelper.onInterceptTouchEvent(ev); - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { if (result) { mLastInterceptWasDragDownHelper = true; if (ev.getAction() == MotionEvent.ACTION_DOWN) { @@ -501,7 +502,7 @@ public class NotificationShadeWindowViewController implements Dumpable { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); mStackScrollLayout.onInterceptTouchEvent(cancellation); - if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mNotificationPanelViewController.handleExternalInterceptTouch(cancellation); } cancellation.recycle(); @@ -516,7 +517,7 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarKeyguardViewManager.onTouch(ev)) { return true; } - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen handled |= mDragDownHelper.onTouchEvent(ev) || handled; @@ -602,7 +603,7 @@ public class NotificationShadeWindowViewController implements Dumpable { } private boolean didNotificationPanelInterceptEvent(MotionEvent ev) { - if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled()) { // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need // to also ask NotificationPanelViewController directly, in order to process swipe up // events originating from notifications diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 866cfb443b0a..9c8a286b2918 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.fragments.FragmentService +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.plugins.qs.QS @@ -129,7 +130,6 @@ class NotificationsQSContainerController @Inject constructor( isGestureNavigation = QuickStepContract.isGesturalMode(currentMode) mView.setStackScroller(notificationStackScrollLayoutController.getView()) - mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL)) if (featureFlags.isEnabled(Flags.QS_CONTAINER_GRAPH_OPTIMIZER)){ mView.enableGraphOptimization() } @@ -283,7 +283,7 @@ class NotificationsQSContainerController @Inject constructor( } private fun setNotificationsConstraints(constraintSet: ConstraintSet) { - if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (KeyguardShadeMigrationNssl.isEnabled) { return } val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java index af44c4e36315..de3d16a57a1f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java @@ -32,6 +32,7 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.res.R; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; @@ -59,7 +60,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout private QS mQs; private View mQSContainer; private int mLastQSPaddingBottom; - private boolean mIsMigratingNSSL; /** * These are used to compute the bounding box containing the shade and the notification scrim, @@ -180,10 +180,6 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout super.dispatchDraw(canvas); } - void setMigratingNSSL(boolean isMigrating) { - mIsMigratingNSSL = isMigrating; - } - void enableGraphOptimization() { setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH); } @@ -196,7 +192,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - if (mIsMigratingNSSL) { + if (KeyguardShadeMigrationNssl.isEnabled()) { return super.drawChild(canvas, child, drawingTime); } int layoutIndex = mLayoutDrawingOrder.indexOf(child); diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS index c8b6a2e9761b..bbcf10be5ff0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS @@ -6,8 +6,12 @@ per-file *Notification* = file:../statusbar/notification/OWNERS per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com -per-file *ShadeHeader* = kozynski@google.com, asc@google.com -per-file *Shade* = justinweir@google.com +per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com + +per-file *Interactor* = set noparent +per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com +per-file *Repository* = set noparent +per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index d73fa1460bd4..335e65e71ae4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -68,10 +68,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.classifier.Classifier; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -107,12 +106,12 @@ import com.android.systemui.util.kotlin.JavaAdapter; import dalvik.annotation.optimization.NeverCompile; -import dagger.Lazy; - import java.io.PrintWriter; import javax.inject.Inject; +import dagger.Lazy; + /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ @@ -155,7 +154,6 @@ public class QuickSettingsController implements Dumpable { private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private final CastController mCastController; private final SplitShadeStateController mSplitShadeStateController; - private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final ShadeRepository mShadeRepository; private final ShadeInteractor mShadeInteractor; @@ -209,12 +207,6 @@ public class QuickSettingsController implements Dumpable { /** Indicates QS is at its max height */ private boolean mFullyExpanded; - /** - * Determines if QS should be already expanded when expanding shade. - * Used for split shade, two finger gesture as well as accessibility shortcut to QS. - * It needs to be set when movement starts as it resets at the end of expansion/collapse. - */ - private boolean mExpandImmediate; private boolean mExpandedWhenExpandingStarted; private boolean mAnimatingHiddenFromCollapsed; private boolean mVisible; @@ -333,7 +325,6 @@ public class QuickSettingsController implements Dumpable { AccessibilityManager accessibilityManager, LockscreenGestureLogger lockscreenGestureLogger, MetricsLogger metricsLogger, - FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, ShadeLogger shadeLog, DumpManager dumpManager, @@ -384,7 +375,6 @@ public class QuickSettingsController implements Dumpable { mShadeLog = shadeLog; mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mCastController = castController; - mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; @@ -516,7 +506,7 @@ public class QuickSettingsController implements Dumpable { /** */ @VisibleForTesting boolean isExpandImmediate() { - return mExpandImmediate; + return mShadeRepository.getLegacyExpandImmediate().getValue(); } float getInitialTouchY() { @@ -606,7 +596,7 @@ public class QuickSettingsController implements Dumpable { // close the whole shade with one motion. Also this will be always true when closing // split shade as there QS are always expanded so every collapsing motion is motion from // expanded QS to closed panel - return mExpandImmediate || (getExpanded() + return isExpandImmediate() || (getExpanded() && !isTracking() && !isExpansionAnimating() && !mExpansionFromOverscroll); } @@ -724,7 +714,9 @@ public class QuickSettingsController implements Dumpable { /** Closes the Qs customizer. */ public void closeQsCustomizer() { - mQs.closeCustomizer(); + if (mQs != null) { + mQs.closeCustomizer(); + } } /** Returns whether touches from the notification panel should be disallowed */ @@ -794,7 +786,7 @@ public class QuickSettingsController implements Dumpable { && mBarState == SHADE) { Log.wtf(TAG, "setting QS height to 0 in split shade while shade is open(ing). " - + "Value of mExpandImmediate = " + mExpandImmediate); + + "Value of isExpandImmediate() = " + isExpandImmediate()); } int maxHeight = getMaxExpansionHeight(); height = Math.min(Math.max( @@ -943,10 +935,9 @@ public class QuickSettingsController implements Dumpable { } void setExpandImmediate(boolean expandImmediate) { - if (expandImmediate != mExpandImmediate) { + if (expandImmediate != isExpandImmediate()) { mShadeLog.logQsExpandImmediateChanged(expandImmediate); - mExpandImmediate = expandImmediate; - mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); + mShadeRepository.setLegacyExpandImmediate(expandImmediate); } } @@ -982,6 +973,7 @@ public class QuickSettingsController implements Dumpable { void updateQsState() { boolean qsFullScreen = getExpanded() && !mSplitShadeEnabled; + mShadeRepository.setLegacyQsFullscreen(qsFullScreen); mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); mNotificationStackScrollLayoutController.setScrollingEnabled( mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll)); @@ -998,7 +990,7 @@ public class QuickSettingsController implements Dumpable { public void updateExpansion() { if (mQs == null) return; final float squishiness; - if ((mExpandImmediate || getExpanded()) && !mSplitShadeEnabled) { + if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) { squishiness = 1; } else if (mTransitioningToFullShadeProgress > 0.0f) { squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction(); @@ -1776,7 +1768,7 @@ public class QuickSettingsController implements Dumpable { // Dragging down on the lockscreen statusbar should prohibit other interactions // immediately, otherwise we'll wait on the touchslop. This is to allow // dragging down to expanded quick settings directly on the lockscreen. - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } } @@ -1821,7 +1813,7 @@ public class QuickSettingsController implements Dumpable { && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept( mInitialTouchX, mInitialTouchY, h)) { - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mPanelView.getParent().requestDisallowInterceptTouchEvent(true); } mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); @@ -2079,8 +2071,8 @@ public class QuickSettingsController implements Dumpable { ipw.println(getExpanded()); ipw.print("mFullyExpanded="); ipw.println(mFullyExpanded); - ipw.print("mExpandImmediate="); - ipw.println(mExpandImmediate); + ipw.print("isExpandImmediate()="); + ipw.println(isExpandImmediate()); ipw.print("mExpandedWhenExpandingStarted="); ipw.println(mExpandedWhenExpandingStarted); ipw.print("mAnimatingHiddenFromCollapsed="); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt index 7a803867d4a4..53eccfdf70d5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt @@ -17,6 +17,8 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorEmptyImpl import dagger.Binds import dagger.Module @@ -30,4 +32,8 @@ abstract class ShadeEmptyImplModule { @Binds @SysUISingleton abstract fun bindsShadeController(sc: ShadeControllerEmptyImpl): ShadeController + + @Binds + @SysUISingleton + abstract fun bindsShadeInteractor(si: ShadeInteractorEmptyImpl): ShadeInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index fca98f580702..e20534cc7840 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -169,12 +169,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { } } - fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) { - for (cb in shadeStateEventsListeners) { - cb.onExpandImmediateChanged(expandImmediateEnabled) - } - } - private fun debugLog(msg: String) { if (!DEBUG) return Log.v(TAG, msg) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 89aaaafbcdf3..54467cffa401 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -17,13 +17,40 @@ package com.android.systemui.shade import com.android.systemui.dagger.SysUISingleton - +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.BaseShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl import dagger.Binds import dagger.Module +import dagger.Provides +import javax.inject.Provider /** Module for classes related to the notification shade. */ @Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class]) abstract class ShadeModule { + companion object { + @Provides + @SysUISingleton + fun provideBaseShadeInteractor( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, + sceneContainerOff: Provider<ShadeInteractorLegacyImpl> + ): BaseShadeInteractor { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } + } + + @Binds + @SysUISingleton + abstract fun bindsShadeInteractor(si: ShadeInteractorImpl): ShadeInteractor + @Binds @SysUISingleton abstract fun bindsShadeViewController( diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt index 5804040a8676..c8511d76f5f0 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt @@ -35,16 +35,5 @@ interface ShadeStateEvents { * Invoked when the notification panel starts or stops launching an [android.app.Activity]. */ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {} - - /** - * Invoked when the "expand immediate" attribute changes. - * - * An example of expanding immediately is when swiping down from the top with two fingers. - * Instead of going to QQS, we immediately expand to full QS. - * - * Another example is when full QS is showing, and we swipe up from the bottom. Instead of - * going to QQS, the panel fully collapses. - */ - fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index 8bab6696e2d3..47b08fe037a4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -93,6 +93,29 @@ interface ShadeRepository { */ @Deprecated("Use ShadeInteractor instead") val legacyIsQsExpanded: StateFlow<Boolean> + /** + * QuickSettingsController.mExpandImmediate as a flow. Indicates that Quick Settings is being + * expanded without first expanding the Shade or Quick Settings is being collapsed without first + * collapsing to shade, i.e. expanding with 2-finger swipe or collapsing by flinging from the + * bottom of the screen. Replaced by ShadeInteractor.isQsBypassingShade. + */ + @Deprecated("Use ShadeInteractor.isQsBypassingShade instead") + val legacyExpandImmediate: StateFlow<Boolean> + + /** True when QS is taking up the entire screen, i.e. fully expanded on a non-unfolded phone. */ + @Deprecated("Use ShadeInteractor instead") val legacyQsFullscreen: StateFlow<Boolean> + + /** */ + @Deprecated("Use ShadeInteractor instead") + fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) + + /** + * Sets whether Quick Settings is being expanded without first expanding the Shade or Quick + * Settings is being collapsed without first collapsing to shade. + */ + @Deprecated("Use ShadeInteractor instead") + fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) + /** Sets whether QS is expanded. */ @Deprecated("Use ShadeInteractor instead") fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) @@ -105,12 +128,13 @@ interface ShadeRepository { fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) /** Sets whether the user is moving Quick Settings with a pointer */ - fun setLegacyQsTracking(legacyQsTracking: Boolean) + @Deprecated("Use ShadeInteractor instead") fun setLegacyQsTracking(legacyQsTracking: Boolean) /** Sets whether the user is moving the shade with a pointer */ - fun setLegacyShadeTracking(tracking: Boolean) + @Deprecated("Use ShadeInteractor instead") fun setLegacyShadeTracking(tracking: Boolean) /** Sets whether the user is moving the shade with a pointer, on lockscreen only */ + @Deprecated("Use ShadeInteractor instead") fun setLegacyLockscreenShadeTracking(tracking: Boolean) /** Amount shade has expanded with regard to the UDFPS location */ @@ -199,6 +223,22 @@ constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepos @Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded: StateFlow<Boolean> = _legacyIsQsExpanded.asStateFlow() + private val _legacyExpandImmediate = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyExpandImmediate: StateFlow<Boolean> = _legacyExpandImmediate.asStateFlow() + + private val _legacyQsFullscreen = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow() + + override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) { + _legacyQsFullscreen.value = legacyQsFullscreen + } + + override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) { + _legacyExpandImmediate.value = legacyExpandImmediate + } + @Deprecated("Use ShadeInteractor instead") override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) { _legacyIsQsExpanded.value = legacyIsQsExpanded diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index d687ef64aec4..6a9757f3adfd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -16,149 +16,42 @@ package com.android.systemui.shade.domain.interactor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.DozeStateModel -import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.shade.data.repository.ShadeRepository -import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor -import com.android.systemui.statusbar.phone.DozeParameters -import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository -import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository -import com.android.systemui.user.domain.interactor.UserSwitcherInteractor -import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.isActive /** Business logic for shade interactions. */ -@OptIn(ExperimentalCoroutinesApi::class) -@SysUISingleton -class ShadeInteractor -@Inject -constructor( - @Application scope: CoroutineScope, - deviceProvisioningRepository: DeviceProvisioningRepository, - disableFlagsRepository: DisableFlagsRepository, - dozeParams: DozeParameters, - sceneContainerFlags: SceneContainerFlags, - // TODO(b/300258424) convert to direct reference instead of provider - sceneInteractorProvider: Provider<SceneInteractor>, - keyguardRepository: KeyguardRepository, - keyguardTransitionInteractor: KeyguardTransitionInteractor, - powerInteractor: PowerInteractor, - userSetupRepository: UserSetupRepository, - userSwitcherInteractor: UserSwitcherInteractor, - sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, - private val repository: ShadeRepository, -) { +interface ShadeInteractor : BaseShadeInteractor { /** Emits true if the shade is currently allowed and false otherwise. */ - val isShadeEnabled: StateFlow<Boolean> = - disableFlagsRepository.disableFlags - .map { it.isShadeEnabled() } - .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + val isShadeEnabled: StateFlow<Boolean> - /** - * Whether split shade, the combined notifications and quick settings shade used for large - * screens, is enabled. - */ - val isSplitShadeEnabled: Flow<Boolean> = - sharedNotificationContainerInteractor.configurationBasedDimensions - .map { dimens -> dimens.useSplitShade } - .distinctUntilChanged() + /** Whether either the shade or QS is fully expanded. */ + val isAnyFullyExpanded: Flow<Boolean> - /** The amount [0-1] that the shade has been opened */ - val shadeExpansion: Flow<Float> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.Shade) - } else { - combine( - repository.lockscreenShadeExpansion, - keyguardRepository.statusBarState, - repository.legacyShadeExpansion, - repository.qsExpansion, - isSplitShadeEnabled - ) { - lockscreenShadeExpansion, - statusBarState, - legacyShadeExpansion, - qsExpansion, - splitShadeEnabled -> - when (statusBarState) { - // legacyShadeExpansion is 1 instead of 0 when QS is expanded - StatusBarState.SHADE -> - if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion - StatusBarState.KEYGUARD -> lockscreenShadeExpansion - // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when - // the pointer is lifted and the lockscreen shade is fully expanded - StatusBarState.SHADE_LOCKED -> 1f - } - } - .distinctUntilChanged() - } + /** Whether the Shade is fully expanded. */ + val isShadeFullyExpanded: Flow<Boolean> /** - * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will - * report 0f. If split shade is enabled, value matches shadeExpansion. + * Whether the user is expanding or collapsing either the shade or quick settings with user + * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended + * but a transition they initiated is still animating. */ - val qsExpansion: StateFlow<Float> = - if (sceneContainerFlags.isEnabled()) { - val qsExp = sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings) - combine(isSplitShadeEnabled, shadeExpansion, qsExp) { - isSplitShadeEnabled, - shadeExp, - qsExp -> - if (isSplitShadeEnabled) { - shadeExp - } else { - qsExp - } - } - .stateIn(scope, SharingStarted.Eagerly, 0f) - } else { - repository.qsExpansion - } + val isUserInteracting: Flow<Boolean> - /** Whether Quick Settings is expanded a non-zero amount. */ - val isQsExpanded: StateFlow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - qsExpansion - .map { it > 0 } - .distinctUntilChanged() - .stateIn(scope, SharingStarted.Eagerly, false) - } else { - repository.legacyIsQsExpanded - } + /** Are touches allowed on the notification panel? */ + val isShadeTouchable: Flow<Boolean> - /** The amount [0-1] either QS or the shade has been opened. */ - val anyExpansion: StateFlow<Float> = - combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } - .stateIn(scope, SharingStarted.Eagerly, 0f) + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ + val isExpandToQsEnabled: Flow<Boolean> +} - /** Whether either the shade or QS is fully expanded. */ - val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged() +/** ShadeInteractor methods with implementations that differ between non-empty impls. */ +interface BaseShadeInteractor { + /** The amount [0-1] either QS or the shade has been opened. */ + val anyExpansion: StateFlow<Float> /** * Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At @@ -169,149 +62,53 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: StateFlow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } - .stateIn(scope, SharingStarted.Eagerly, false) + val isAnyExpanded: StateFlow<Boolean> + + /** The amount [0-1] that the shade has been opened. */ + val shadeExpansion: Flow<Float> /** - * Whether the user is expanding or collapsing the shade with user input. This will be true even - * if the user's input gesture has ended but a transition they initiated is animating. + * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will + * report 0f. If split shade is enabled, value matches shadeExpansion. */ - val isUserInteractingWithShade: Flow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.Shade) - } else { - combine( - userInteractingFlow( - repository.legacyShadeTracking, - repository.legacyShadeExpansion - ), - repository.legacyLockscreenShadeTracking - ) { legacyShadeTracking, legacyLockscreenShadeTracking -> - legacyShadeTracking || legacyLockscreenShadeTracking - } - } + val qsExpansion: StateFlow<Float> + + /** Whether Quick Settings is expanded a non-zero amount. */ + val isQsExpanded: StateFlow<Boolean> /** - * Whether the user is expanding or collapsing quick settings with user input. This will be true - * even if the user's input gesture has ended but a transition they initiated is still - * animating. + * Emits true whenever Quick Settings is being expanded without first expanding the Shade or if + * if Quick Settings is being collapsed without first collapsing to shade, i.e. expanding with + * 2-finger swipe or collapsing by flinging from the bottom of the screen. This concept was + * previously called "expand immediate" in the legacy codebase. */ - val isUserInteractingWithQs: Flow<Boolean> = - if (sceneContainerFlags.isEnabled()) { - sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.QuickSettings) - } else { - userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) - } + val isQsBypassingShade: Flow<Boolean> /** - * Whether the user is expanding or collapsing either the shade or quick settings with user - * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended - * but a transition they initiated is still animating. + * Emits true when QS is displayed over the entire screen of the device. Currently, this only + * happens on phones that are not unfolded when QS expansion is equal to 1. */ - val isUserInteracting: Flow<Boolean> = - combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } - .distinctUntilChanged() - - /** Are touches allowed on the notification panel? */ - val isShadeTouchable: Flow<Boolean> = - combine( - powerInteractor.isAsleep, - keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD }, - keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }, - deviceProvisioningRepository.isFactoryResetProtectionActive, - ) { isAsleep, goingToSleep, isPulsing, isFrpActive -> - when { - // Touches are disabled when Factory Reset Protection is active - isFrpActive -> false - // If the device is going to sleep, only accept touches if we're still - // animating - goingToSleep -> dozeParams.shouldControlScreenOff() - // If the device is asleep, only accept touches if there's a pulse - isAsleep -> isPulsing - else -> true - } - } - - /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ - val isExpandToQsEnabled: Flow<Boolean> = - combine( - disableFlagsRepository.disableFlags, - isShadeEnabled, - keyguardRepository.isDozing, - userSetupRepository.isUserSetupFlow, - deviceProvisioningRepository.isDeviceProvisioned, - ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned -> - isDeviceProvisioned && - // Disallow QS during setup if it's a simple user switcher. (The user intends to - // use the lock screen user switcher, QS is not needed.) - (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) && - isShadeEnabled && - disableFlags.isQuickSettingsEnabled() && - !isDozing - } + val isQsFullscreen: Flow<Boolean> - fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = - sceneInteractor.transitionState - .flatMapLatest { state -> - when (state) { - is ObservableTransitionState.Idle -> - if (state.scene == sceneKey) { - flowOf(1f) - } else { - flowOf(0f) - } - is ObservableTransitionState.Transition -> - if (state.toScene == sceneKey) { - state.progress - } else if (state.fromScene == sceneKey) { - state.progress.map { progress -> 1 - progress } - } else { - flowOf(0f) - } - } - } - .distinctUntilChanged() - - fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = - sceneInteractor.transitionState - .map { state -> - when (state) { - is ObservableTransitionState.Idle -> false - is ObservableTransitionState.Transition -> - state.isInitiatedByUserInput && - (state.toScene == sceneKey || state.fromScene == sceneKey) - } - } - .distinctUntilChanged() + /** + * Whether the user is expanding or collapsing the shade with user input. This will be true even + * if the user's input gesture has ended but a transition they initiated is animating. + */ + val isUserInteractingWithShade: Flow<Boolean> /** - * Return a flow for whether a user is interacting with an expandable shade component using - * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that - * [expansion.first] checks the current value of the flow. + * Whether the user is expanding or collapsing quick settings with user input. This will be true + * even if the user's input gesture has ended but a transition they initiated is still + * animating. */ - private fun userInteractingFlow( - tracking: Flow<Boolean>, - expansion: StateFlow<Float> - ): Flow<Boolean> { - return flow { - // initial value is false - emit(false) - while (currentCoroutineContext().isActive) { - // wait for tracking to become true - tracking.first { it } - emit(true) - // wait for tracking to become false - tracking.first { !it } - // wait for expansion to complete in either direction - expansion.first { it <= 0f || it >= 1f } - // interaction complete - emit(false) - } - } - } + val isUserInteractingWithQs: Flow<Boolean> +} + +fun createAnyExpansionFlow( + scope: CoroutineScope, + shadeExpansion: Flow<Float>, + qsExpansion: Flow<Float> +): StateFlow<Float> { + return combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } + .stateIn(scope, SharingStarted.Eagerly, 0f) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt new file mode 100644 index 000000000000..d41c5a66ad82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Empty implementation of ShadeInteractor for System UI variants with no shade. */ +@SysUISingleton +class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { + private val inactiveFlowBoolean = MutableStateFlow(false) + private val inactiveFlowFloat = MutableStateFlow(0f) + override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean + override val shadeExpansion: Flow<Float> = inactiveFlowFloat + override val qsExpansion: StateFlow<Float> = inactiveFlowFloat + override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean + override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean + override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean + override val anyExpansion: StateFlow<Float> = inactiveFlowFloat + override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean + override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean + override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean + override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean + override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean + override val isUserInteracting: Flow<Boolean> = inactiveFlowBoolean + override val isShadeTouchable: Flow<Boolean> = inactiveFlowBoolean + override val isExpandToQsEnabled: Flow<Boolean> = inactiveFlowBoolean +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt new file mode 100644 index 000000000000..68600e9d6918 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository +import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository +import com.android.systemui.user.domain.interactor.UserSwitcherInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** The non-empty SceneInteractor implementation. */ +@SysUISingleton +class ShadeInteractorImpl +@Inject +constructor( + @Application val scope: CoroutineScope, + deviceProvisioningRepository: DeviceProvisioningRepository, + disableFlagsRepository: DisableFlagsRepository, + dozeParams: DozeParameters, + keyguardRepository: KeyguardRepository, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + powerInteractor: PowerInteractor, + userSetupRepository: UserSetupRepository, + userSwitcherInteractor: UserSwitcherInteractor, + private val baseShadeInteractor: BaseShadeInteractor, +) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor { + override val isShadeEnabled: StateFlow<Boolean> = + disableFlagsRepository.disableFlags + .map { it.isShadeEnabled() } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + + override val isAnyFullyExpanded: Flow<Boolean> = + anyExpansion.map { it >= 1f }.distinctUntilChanged() + + override val isShadeFullyExpanded: Flow<Boolean> = + baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged() + + override val isUserInteracting: Flow<Boolean> = + combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } + .distinctUntilChanged() + + override val isShadeTouchable: Flow<Boolean> = + combine( + powerInteractor.isAsleep, + keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD }, + keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING }, + deviceProvisioningRepository.isFactoryResetProtectionActive, + ) { isAsleep, goingToSleep, isPulsing, isFrpActive -> + when { + // Touches are disabled when Factory Reset Protection is active + isFrpActive -> false + // If the device is going to sleep, only accept touches if we're still + // animating + goingToSleep -> dozeParams.shouldControlScreenOff() + // If the device is asleep, only accept touches if there's a pulse + isAsleep -> isPulsing + else -> true + } + } + + override val isExpandToQsEnabled: Flow<Boolean> = + combine( + disableFlagsRepository.disableFlags, + isShadeEnabled, + keyguardRepository.isDozing, + userSetupRepository.isUserSetupFlow, + deviceProvisioningRepository.isDeviceProvisioned, + ) { disableFlags, isShadeEnabled, isDozing, isUserSetup, isDeviceProvisioned -> + isDeviceProvisioned && + // Disallow QS during setup if it's a simple user switcher. (The user intends to + // use the lock screen user switcher, QS is not needed.) + (isUserSetup || !userSwitcherInteractor.isSimpleUserSwitcher) && + isShadeEnabled && + disableFlags.isQuickSettingsEnabled() && + !isDozing + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt new file mode 100644 index 000000000000..2ac3193d1e8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.isActive + +/** ShadeInteractor implementation for the legacy codebase, e.g. NPVC. */ +@SysUISingleton +class ShadeInteractorLegacyImpl +@Inject +constructor( + @Application val scope: CoroutineScope, + keyguardRepository: KeyguardRepository, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, + repository: ShadeRepository, +) : BaseShadeInteractor { + /** The amount [0-1] that the shade has been opened */ + override val shadeExpansion: Flow<Float> = + combine( + repository.lockscreenShadeExpansion, + keyguardRepository.statusBarState, + repository.legacyShadeExpansion, + repository.qsExpansion, + sharedNotificationContainerInteractor.isSplitShadeEnabled + ) { + lockscreenShadeExpansion, + statusBarState, + legacyShadeExpansion, + qsExpansion, + splitShadeEnabled -> + when (statusBarState) { + // legacyShadeExpansion is 1 instead of 0 when QS is expanded + StatusBarState.SHADE -> + if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion + StatusBarState.KEYGUARD -> lockscreenShadeExpansion + // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when + // the pointer is lifted and the lockscreen shade is fully expanded + StatusBarState.SHADE_LOCKED -> 1f + } + } + .distinctUntilChanged() + + override val qsExpansion: StateFlow<Float> = repository.qsExpansion + + override val isQsExpanded: StateFlow<Boolean> = repository.legacyIsQsExpanded + + override val isQsBypassingShade: Flow<Boolean> = repository.legacyExpandImmediate + override val isQsFullscreen: Flow<Boolean> = repository.legacyQsFullscreen + + override val anyExpansion: StateFlow<Float> = + createAnyExpansionFlow(scope, shadeExpansion, qsExpansion) + + override val isAnyExpanded = + repository.legacyExpandedOrAwaitingInputTransfer.stateIn( + scope, + SharingStarted.Eagerly, + false + ) + + override val isUserInteractingWithShade: Flow<Boolean> = + combine( + userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion), + repository.legacyLockscreenShadeTracking + ) { legacyShadeTracking, legacyLockscreenShadeTracking -> + legacyShadeTracking || legacyLockscreenShadeTracking + } + + override val isUserInteractingWithQs: Flow<Boolean> = + userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion) + + /** + * Return a flow for whether a user is interacting with an expandable shade component using + * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that + * [expansion.first] checks the current value of the flow. + */ + private fun userInteractingFlow( + tracking: Flow<Boolean>, + expansion: StateFlow<Float> + ): Flow<Boolean> { + return flow { + // initial value is false + emit(false) + while (currentCoroutineContext().isActive) { + // wait for tracking to become true + tracking.first { it } + emit(true) + // wait for tracking to become false + tracking.first { !it } + // wait for expansion to complete in either direction + expansion.first { it <= 0f || it >= 1f } + // interaction complete + emit(false) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt new file mode 100644 index 000000000000..7cff8ea7abd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shade.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** ShadeInteractor implementation for Scene Container. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class ShadeInteractorSceneContainerImpl +@Inject +constructor( + @Application scope: CoroutineScope, + sceneInteractor: SceneInteractor, + sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, +) : BaseShadeInteractor { + override val shadeExpansion: Flow<Float> = sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + + private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + + override val qsExpansion: StateFlow<Float> = + combine( + sharedNotificationContainerInteractor.isSplitShadeEnabled, + shadeExpansion, + sceneBasedQsExpansion, + ) { isSplitShadeEnabled, shadeExpansion, qsExpansion -> + if (isSplitShadeEnabled) { + shadeExpansion + } else { + qsExpansion + } + } + .stateIn(scope, SharingStarted.Eagerly, 0f) + + override val isQsExpanded: StateFlow<Boolean> = + qsExpansion + .map { it > 0 } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) + + override val isQsBypassingShade: Flow<Boolean> = + sceneInteractor.transitionState + .flatMapLatest { state -> + when (state) { + is ObservableTransitionState.Idle -> flowOf(false) + is ObservableTransitionState.Transition -> + flowOf( + state.toScene == SceneKey.QuickSettings && + state.fromScene != SceneKey.Shade + ) + } + } + .distinctUntilChanged() + + override val isQsFullscreen: Flow<Boolean> = + sceneInteractor.transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> state.scene == SceneKey.QuickSettings + is ObservableTransitionState.Transition -> false + } + } + .distinctUntilChanged() + + override val anyExpansion: StateFlow<Float> = + createAnyExpansionFlow(scope, shadeExpansion, qsExpansion) + + override val isAnyExpanded = + anyExpansion + .map { it > 0f } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.Eagerly, false) + + override val isUserInteractingWithShade: Flow<Boolean> = + sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + + override val isUserInteractingWithQs: Flow<Boolean> = + sceneBasedInteracting(sceneInteractor, SceneKey.QuickSettings) + + /** + * Returns a flow that uses scene transition progress to and from a scene that is pulled down + * from the top of the screen to a 0-1 expansion amount float. + */ + internal fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = + sceneInteractor.transitionState + .flatMapLatest { state -> + when (state) { + is ObservableTransitionState.Idle -> + if (state.scene == sceneKey) { + flowOf(1f) + } else { + flowOf(0f) + } + is ObservableTransitionState.Transition -> + if (state.toScene == sceneKey) { + state.progress + } else if (state.fromScene == sceneKey) { + state.progress.map { progress -> 1 - progress } + } else { + flowOf(0f) + } + } + } + .distinctUntilChanged() + + /** + * Returns a flow that uses scene transition data to determine whether the user is interacting + * with a scene that is pulled down from the top of the screen. + */ + internal fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) = + sceneInteractor.transitionState + .map { state -> + when (state) { + is ObservableTransitionState.Idle -> false + is ObservableTransitionState.Transition -> + state.isInitiatedByUserInput && + (state.toScene == sceneKey || state.fromScene == sceneKey) + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index 20b9edee2d70..af880816914f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -19,13 +19,14 @@ package com.android.systemui.shade.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.SceneKey -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject /** Models UI state and handles user input for the shade scene. */ @SysUISingleton @@ -34,6 +35,7 @@ class ShadeSceneViewModel constructor( @Application private val applicationScope: CoroutineScope, private val deviceEntryInteractor: DeviceEntryInteractor, + val qsSceneAdapter: QSSceneAdapter, val shadeHeaderViewModel: ShadeHeaderViewModel, ) { /** The key of the scene we should switch to when swiping up. */ diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt index ab0d6e3a6382..922560f16dce 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/config/BcSmartspaceConfigProvider.kt @@ -17,11 +17,10 @@ package com.android.systemui.smartspace.config import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.BcSmartspaceConfigPlugin class BcSmartspaceConfigProvider(private val featureFlags: FeatureFlags) : BcSmartspaceConfigPlugin { override val isDefaultDateWeatherDisabled: Boolean - get() = featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) + get() = true } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index c2863fb2c330..d88fab0deaa3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -75,14 +75,14 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.policy.CallbackController; +import dagger.Lazy; + import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; -import dagger.Lazy; - /** * This class takes the functions from IStatusBar that come in on * binder pool threads and posts messages to get them onto the main @@ -424,7 +424,7 @@ public class CommandQueue extends IStatusBar.Stub implements default void onTracingStateChanged(boolean enabled) { } /** - * Requests {@link com.android.systemui.accessibility.WindowMagnification} to invoke + * Requests {@link com.android.systemui.accessibility.Magnification} to invoke * {@code android.view.accessibility.AccessibilityManager# * setWindowMagnificationConnection(IWindowMagnificationConnection)} * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 2e3f3f894687..ae765e40c790 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -21,7 +21,9 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver import com.android.systemui.media.controls.ui.MediaHierarchyManager +import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager @@ -78,7 +80,8 @@ constructor( private val shadeRepository: ShadeRepository, private val shadeInteractor: ShadeInteractor, private val powerInteractor: PowerInteractor, - private val splitShadeStateController: SplitShadeStateController + private val splitShadeStateController: SplitShadeStateController, + private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver, ) : Dumpable { private var pulseHeight: Float = 0f @@ -157,7 +160,8 @@ constructor( var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null /** The touch helper responsible for the drag down animation. */ - val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context) + val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, + naturalScrollingSettingObserver, context) private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy { splitShadeOverScrollerFactory.create({ qS }, { nsslController }) @@ -751,6 +755,7 @@ class DragDownHelper( private val falsingManager: FalsingManager, private val falsingCollector: FalsingCollector, private val dragDownCallback: LockscreenShadeTransitionController, + private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver, context: Context ) : Gefingerpoken { @@ -765,6 +770,7 @@ class DragDownHelper( private var draggedFarEnough = false private var startingChild: ExpandableView? = null private var lastHeight = 0f + private var isTrackpadReverseScroll = false var isDraggingDown = false private set @@ -802,9 +808,11 @@ class DragDownHelper( startingChild = null initialTouchY = y initialTouchX = x + isTrackpadReverseScroll = !naturalScrollingSettingObserver.isNaturalScrollingEnabled + && isTrackpadScroll(true, event) } MotionEvent.ACTION_MOVE -> { - val h = y - initialTouchY + val h = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY) // Adjust the touch slop if another gesture may be being performed. val touchSlop = if (event.classification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) { @@ -834,7 +842,7 @@ class DragDownHelper( val y = event.y when (event.actionMasked) { MotionEvent.ACTION_MOVE -> { - lastHeight = y - initialTouchY + lastHeight = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY) captureStartingChild(initialTouchX, initialTouchY) dragDownCallback.dragDownAmount = lastHeight + dragDownAmountOnStart if (startingChild != null) { @@ -859,12 +867,14 @@ class DragDownHelper( !isFalseTouch && dragDownCallback.canDragDown() ) { - dragDownCallback.onDraggedDown(startingChild, (y - initialTouchY).toInt()) + val dragDown = (if (isTrackpadReverseScroll) -1 else 1) * (y - initialTouchY) + dragDownCallback.onDraggedDown(startingChild, dragDown.toInt()) if (startingChild != null) { expandCallback.setUserLockedChild(startingChild, false) startingChild = null } isDraggingDown = false + isTrackpadReverseScroll = false } else { stopDragging() return false @@ -943,6 +953,7 @@ class DragDownHelper( startingChild = null } isDraggingDown = false + isTrackpadReverseScroll = false dragDownCallback.onDragDownReset() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 0b6e4009c4f7..4d373353d7a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -92,6 +92,7 @@ public class NotificationShelf extends ActivatableNotificationView { private int mIndexOfFirstViewInShelf = -1; private float mCornerAnimationDistance; private float mActualWidth = -1; + private int mMaxIconsOnLockscreen; private final RefactorFlag mSensitiveRevealAnim = RefactorFlag.forView(Flags.SENSITIVE_REVEAL_ANIM); private boolean mCanModifyColorOfNotifications; @@ -136,6 +137,7 @@ public class NotificationShelf extends ActivatableNotificationView { Resources res = getResources(); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height); + mMaxIconsOnLockscreen = res.getInteger(R.integer.max_notif_icons_on_lockscreen); ViewGroup.LayoutParams layoutParams = getLayoutParams(); final int newShelfHeight = res.getDimensionPixelOffset(R.dimen.notification_shelf_height); @@ -227,7 +229,9 @@ public class NotificationShelf extends ActivatableNotificationView { } else { viewState.setAlpha(1f - ambientState.getHideAmount()); } - viewState.belowSpeedBump = getSpeedBumpIndex() == 0; + if (!NotificationIconContainerRefactor.isEnabled()) { + viewState.belowSpeedBump = getSpeedBumpIndex() == 0; + } viewState.hideSensitive = false; viewState.setXTranslation(getTranslationX()); viewState.hasItemsInStableShelf = lastViewState.inShelf; @@ -271,6 +275,7 @@ public class NotificationShelf extends ActivatableNotificationView { } private int getSpeedBumpIndex() { + NotificationIconContainerRefactor.assertInLegacyMode(); return mHostLayout.getSpeedBumpIndex(); } @@ -280,6 +285,7 @@ public class NotificationShelf extends ActivatableNotificationView { */ @VisibleForTesting public void updateActualWidth(float fractionToShade, float shortestWidth) { + NotificationIconContainerRefactor.assertInLegacyMode(); final float actualWidth = mAmbientState.isOnKeyguard() ? MathUtils.lerp(shortestWidth, getWidth(), fractionToShade) : getWidth(); @@ -290,6 +296,15 @@ public class NotificationShelf extends ActivatableNotificationView { mActualWidth = actualWidth; } + private void setActualWidth(float actualWidth) { + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; + setBackgroundWidth((int) actualWidth); + if (mShelfIcons != null) { + mShelfIcons.setActualLayoutWidth((int) actualWidth); + } + mActualWidth = actualWidth; + } + @Override public void getBoundsOnScreen(Rect outRect, boolean clipToParent) { super.getBoundsOnScreen(outRect, clipToParent); @@ -467,12 +482,26 @@ public class NotificationShelf extends ActivatableNotificationView { final float fractionToShade = Interpolators.STANDARD.getInterpolation( mAmbientState.getFractionToShade()); - final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf); - updateActualWidth(fractionToShade, shortestWidth); + + if (NotificationIconContainerRefactor.isEnabled()) { + if (mAmbientState.isOnKeyguard()) { + float numViews = MathUtils.min(numViewsInShelf, mMaxIconsOnLockscreen + 1); + float shortestWidth = mShelfIcons.calculateWidthFor(numViews); + float actualWidth = MathUtils.lerp(shortestWidth, getWidth(), fractionToShade); + setActualWidth(actualWidth); + } else { + setActualWidth(getWidth()); + } + } else { + final float shortestWidth = mShelfIcons.calculateWidthFor(numViewsInShelf); + updateActualWidth(fractionToShade, shortestWidth); + } // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); - mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex()); + if (!NotificationIconContainerRefactor.isEnabled()) { + mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex()); + } mShelfIcons.calculateIconXTranslations(); mShelfIcons.applyIconStates(); for (int i = 0; i < getHostLayoutChildCount(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 2cd55609a749..ef87406036b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -269,8 +269,7 @@ constructor( fun isDateWeatherDecoupled(): Boolean { execution.assertIsMainThread() - return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) && - datePlugin != null && weatherPlugin != null + return datePlugin != null && weatherPlugin != null } fun isWeatherEnabled(): Boolean { @@ -501,8 +500,8 @@ constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { - if (isDateWeatherDecoupled()) { - return t.featureType != SmartspaceTarget.FEATURE_WEATHER + if (isDateWeatherDecoupled() && t.featureType == SmartspaceTarget.FEATURE_WEATHER) { + return false } if (!showNotifications) { return t.featureType == SmartspaceTarget.FEATURE_WEATHER diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index 2ea7f61aee4c..a85c440af688 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -23,23 +23,25 @@ import androidx.annotation.ColorInt import androidx.collection.ArrayMap import androidx.lifecycle.lifecycleScope import com.android.internal.policy.SystemBarUtils +import com.android.internal.statusbar.StatusBarIcon import com.android.internal.util.ContrastColorUtil import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.collection.NotifCollection +import com.android.systemui.statusbar.notification.icon.IconPack import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged -import com.android.systemui.util.children import com.android.systemui.util.kotlin.mapValuesNotNullTo -import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.stateFlow import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating @@ -62,11 +64,18 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerShelfViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: ShelfNotificationIconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { - viewModel.icons.bindIcons(view, configuration, configurationController, viewStore) + viewModel.icons.bindIcons( + view, + configuration, + configurationController, + notifyBindingFailures = { failureTracker.shelfFailures = it }, + viewStore, + ) } } } @@ -77,18 +86,20 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerStatusBarViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: StatusBarNotificationIconViewStore, ): DisposableHandle { val contrastColorUtil = ContrastColorUtil.getInstance(view.context) return view.repeatWhenAttached { lifecycleScope.run { launch { - val iconColors = + val iconColors: Flow<NotificationIconColors> = viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) } viewModel.icons.bindIcons( view, configuration, configurationController, + notifyBindingFailures = { failureTracker.statusBarFailures = it }, viewStore, ) { _, sbiv -> StatusBarIconViewBinder.bindIconColors( @@ -110,15 +121,18 @@ object NotificationIconContainerViewBinder { viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, configuration: ConfigurationState, configurationController: ConfigurationController, + failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: IconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { + view.setUseIncreasedIconScale(true) launch { viewModel.icons.bindIcons( view, configuration, configurationController, + notifyBindingFailures = { failureTracker.aodFailures = it }, viewStore, ) { _, sbiv -> viewModel.bindAodStatusBarIconView(sbiv, configuration) @@ -176,7 +190,7 @@ object NotificationIconContainerViewBinder { } /** - * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s [children]. + * Binds [NotificationIconsViewData] to a [NotificationIconContainer]'s children. * * [bindIcon] will be invoked to bind a child [StatusBarIconView] to an icon associated with the * given `iconKey`. The parent [Job] of this coroutine will be cancelled automatically when the @@ -186,6 +200,7 @@ object NotificationIconContainerViewBinder { view: NotificationIconContainer, configuration: ConfigurationState, configurationController: ConfigurationController, + notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, ): Unit = coroutineScope { @@ -207,113 +222,144 @@ object NotificationIconContainerViewBinder { -> FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } - - launch { - layoutParams.collect { params: FrameLayout.LayoutParams -> - for (child in view.children) { - child.layoutParams = params - } - } - } - - val iconBindings = mutableMapOf<String, Job>() + val failedBindings = mutableSetOf<String>() + val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() - sample(layoutParams, ::Pair).collect { - (iconsData: NotificationIconsViewData, layoutParams: FrameLayout.LayoutParams), - -> + collect { iconsData: NotificationIconsViewData -> val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons) prevIcons = iconsData - val replacingIcons = - iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) -> - viewStore.iconView(v.notifKey).statusBarIcon + // Lookup 1:1 group icon replacements + val replacingIcons: ArrayMap<String, StatusBarIcon> = + iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, notifKey) -> + boundViewsByNotifKey[notifKey]?.first?.statusBarIcon } - view.setReplacingIcons(replacingIcons) - - val childrenByNotifKey: Map<String, StatusBarIconView> = - view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) { - it.notification.key + view.withIconReplacements(replacingIcons) { + // Remove and unbind. + for (notifKey in iconsDiff.removed) { + failedBindings.remove(notifKey) + val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue + view.removeView(child) + job.cancel() } - iconsDiff.removed - .mapNotNull { key -> childrenByNotifKey[key]?.let { key to it } } - .forEach { (key, child) -> - view.removeView(child) - iconBindings.remove(key)?.cancel() + // Add and bind. + val toAdd: Sequence<String> = iconsDiff.added.asSequence() + failedBindings + for ((idx, notifKey) in toAdd.withIndex()) { + // Lookup the StatusBarIconView from the store. + val sbiv = viewStore.iconView(notifKey) + if (sbiv == null) { + failedBindings.add(notifKey) + continue + } + // The view might still be transiently added if it was just removed and added + // again + view.removeTransientView(sbiv) + view.addView(sbiv, idx) + boundViewsByNotifKey.remove(notifKey)?.second?.cancel() + boundViewsByNotifKey[notifKey] = + Pair( + sbiv, + launch { + launch { layoutParams.collect { sbiv.layoutParams = it } } + bindIcon(notifKey, sbiv) + }, + ) } - val toAdd = iconsDiff.added.map { it.notifKey to viewStore.iconView(it.notifKey) } - for ((i, keyAndView) in toAdd.withIndex()) { - val (key, sbiv) = keyAndView - // The view might still be transiently added if it was just removed - // and added again - view.removeTransientView(sbiv) - view.addView(sbiv, i, layoutParams) - iconBindings.remove(key)?.cancel() - iconBindings[key] = launch { bindIcon(key, sbiv) } - } + // Set the maximum number of icons to show in the container. Any icons over this + // amount will render as an "overflow dot". + val maxIconsAmount: Int = + when (iconsData.limitType) { + LimitType.MaximumIndex -> { + iconsData.visibleIcons + .asSequence() + .take(iconsData.iconLimit) + .count { info -> info.notifKey in boundViewsByNotifKey } + } + LimitType.MaximumAmount -> { + iconsData.iconLimit + } + } + view.setMaxIconsAmount(maxIconsAmount) + + // Track the binding failures so that they appear in dumpsys. + notifyBindingFailures(failedBindings) - view.setChangingViewPositions(true) - // Re-sort notification icons - val childCount = view.childCount - for (i in 0 until childCount) { - val actual = view.getChildAt(i) - val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey) - if (actual === expected) { - continue + // Re-sort notification icons + view.changeViewPositions { + val expectedChildren: List<StatusBarIconView> = + iconsData.visibleIcons.mapNotNull { + boundViewsByNotifKey[it.notifKey]?.first + } + val childCount = view.childCount + for (i in 0 until childCount) { + val actual = view.getChildAt(i) + val expected = expectedChildren[i] + if (actual === expected) { + continue + } + view.removeView(expected) + view.addView(expected, i) + } } - view.removeView(expected) - view.addView(expected, i) } - view.setChangingViewPositions(false) - - view.setReplacingIcons(null) + // Recalculate all icon positions, to reflect our updates. + view.calculateIconXTranslations() } } + /** + * Track which groups are being replaced with a different icon instance, but with the same + * visual icon. This prevents a weird animation where it looks like an icon disappears and + * reappears unchanged. + */ + // TODO(b/305739416): Ideally we wouldn't swap out the StatusBarIconView at all, and instead use + // a single SBIV instance for the group. Then this whole concept can go away. + private inline fun <R> NotificationIconContainer.withIconReplacements( + replacements: ArrayMap<String, StatusBarIcon>, + block: () -> R + ): R { + setReplacingIcons(replacements) + return block().also { setReplacingIcons(null) } + } + + /** + * Any invocations of [NotificationIconContainer.addView] / + * [NotificationIconContainer.removeView] inside of [block] will not cause a new add / remove + * animation. + */ + private inline fun <R> NotificationIconContainer.changeViewPositions(block: () -> R): R { + setChangingViewPositions(true) + return block().also { setChangingViewPositions(false) } + } + /** External storage for [StatusBarIconView] instances. */ fun interface IconViewStore { - fun iconView(key: String): StatusBarIconView + fun iconView(key: String): StatusBarIconView? } @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE } /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */ -class ShelfNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.shelfIcon ?: error("No shelf IconView found for key: $key") - } -} +class ShelfNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.shelfIcon }) /** [IconViewStore] for the always-on display. */ class AlwaysOnDisplayNotificationIconViewStore @Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.aodIcon ?: error("No AOD IconView found for key: $key") - } -} +constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.aodIcon }) /** [IconViewStore] for the status bar. */ -class StatusBarNotificationIconViewStore -@Inject -constructor( - private val notifCollection: NotifCollection, -) : IconViewStore { - override fun iconView(key: String): StatusBarIconView { - val entry = notifCollection.getEntry(key) ?: error("No entry found for key: $key") - return entry.icons.statusBarIcon ?: error("No status bar IconView found for key: $key") +class StatusBarNotificationIconViewStore @Inject constructor(notifCollection: NotifCollection) : + IconViewStore by (notifCollection.iconViewStoreBy { it.statusBarIcon }) + +private fun NotifCollection.iconViewStoreBy(block: (IconPack) -> StatusBarIconView?) = + IconViewStore { key -> + getEntry(key)?.icons?.let(block) } -} private val View.viewBounds: Rect get() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt new file mode 100644 index 000000000000..0c114a2ac55d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBindingFailureTracker.kt @@ -0,0 +1,58 @@ +/* + * 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.icon.ui.viewbinder + +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor +import com.android.systemui.util.asIndenting +import com.android.systemui.util.printCollection +import dagger.Binds +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap +import java.io.PrintWriter +import javax.inject.Inject + +@SysUISingleton +class StatusBarIconViewBindingFailureTracker @Inject constructor() : CoreStartable { + + var aodFailures: Collection<String> = emptyList() + var statusBarFailures: Collection<String> = emptyList() + var shelfFailures: Collection<String> = emptyList() + + // TODO(b/310681665): Ideally we wouldn't need to implement CoreStartable at all, and could just + // @Binds @IntoSet the Dumpable. + override fun start() { + // no-op, we're just using this as a dumpable + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + if (!NotificationIconContainerRefactor.isEnabled) return + pw.asIndenting().run { + printCollection("AOD Icon binding failures:", aodFailures) + printCollection("Status Bar Icon binding failures:", statusBarFailures) + printCollection("Shelf Icon binding failures:", shelfFailures) + } + } + + @dagger.Module + interface Module { + @Binds + @IntoMap + @ClassKey(StatusBarIconViewBindingFailureTracker::class) + fun bindStartable(impl: StatusBarIconViewBindingFailureTracker): CoreStartable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt index b11eca2c7d93..9cb60d56f277 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt @@ -15,10 +15,13 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.content.res.Resources import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor import javax.inject.Inject @@ -35,9 +38,12 @@ constructor( iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor, keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, + @Main resources: Resources, shadeInteractor: ShadeInteractor, ) { + private val maxIcons = resources.getInteger(R.integer.max_notif_icons_on_aod) + /** Are changes to the icon container animated? */ val areContainerChangesAnimated: Flow<Boolean> = combine( @@ -67,7 +73,8 @@ constructor( val icons: Flow<NotificationIconsViewData> = iconsInteractor.aodNotifs.map { entries -> NotificationIconsViewData( - visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) }, + visibleIcons = entries.mapNotNull { it.toIconInfo(it.aodIcon) }, + iconLimit = maxIcons, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt index 1560106bfadb..8484fdc92d4f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -29,8 +30,23 @@ constructor( /** [NotificationIconsViewData] indicating which icons to display in the view. */ val icons: Flow<NotificationIconsViewData> = interactor.filteredNotifSet().map { entries -> + var firstAmbient = 0 + val visibleKeys = buildList { + for (entry in entries) { + entry.toIconInfo(entry.shelfIcon)?.let { info -> + add(info) + // NOTE: we assume that all ambient notifications will be at the end of the + // list + if (!entry.isAmbient) { + firstAmbient++ + } + } + } + } NotificationIconsViewData( - visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) }, + visibleKeys, + iconLimit = firstAmbient, + limitType = LimitType.MaximumIndex, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt index 82626acc4b04..af37e49c10f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt @@ -15,9 +15,12 @@ */ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.content.res.Resources import android.graphics.Rect +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor @@ -31,6 +34,7 @@ import com.android.systemui.util.ui.toAnimatedValueFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -43,9 +47,12 @@ constructor( headsUpIconInteractor: HeadsUpNotificationIconInteractor, keyguardInteractor: KeyguardInteractor, notificationsInteractor: ActiveNotificationsInteractor, + @Main resources: Resources, shadeInteractor: ShadeInteractor, ) { + private val maxIcons = resources.getInteger(R.integer.max_notif_static_icons) + /** Are changes to the icon container animated? */ val animationsEnabled: Flow<Boolean> = combine( @@ -76,26 +83,26 @@ constructor( val icons: Flow<NotificationIconsViewData> = iconsInteractor.statusBarNotifs.map { entries -> NotificationIconsViewData( - visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) }, + visibleIcons = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) }, + iconLimit = maxIcons, ) } /** An Icon to show "isolated" in the IconContainer. */ val isolatedIcon: Flow<AnimatedValue<NotificationIconInfo?>> = headsUpIconInteractor.isolatedNotification + .combine(icons) { isolatedNotif, iconsViewData -> + isolatedNotif?.let { + iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif } + } + } .pairwise(initialValue = null) - .sample(combine(icons, shadeInteractor.shadeExpansion, ::Pair)) { - (prev, isolatedNotif), - (iconsViewData, shadeExpansion), - -> - val iconInfo = - isolatedNotif?.let { - iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } - } + .distinctUntilChanged() + .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion -> val animate = when { - isolatedNotif == prev -> false - isolatedNotif == null || prev == null -> shadeExpansion == 0f + iconInfo?.notifKey == prev?.notifKey -> false + iconInfo == null || prev == null -> shadeExpansion == 0f else -> false } AnimatableEvent(iconInfo, animate) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt index 867be84f3a12..e8756d031e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt @@ -23,16 +23,33 @@ import com.android.systemui.util.kotlin.mapValuesNotNullTo /** Encapsulates the collection of notification icons present on the device. */ data class NotificationIconsViewData( /** Icons that are visible in the container. */ - val visibleKeys: List<NotificationIconInfo> = emptyList(), - /** Keys of icons that are "behind" the overflow dot. */ - val collapsedKeys: Set<String> = emptySet(), - /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */ - val forceShowDot: Boolean = false, + val visibleIcons: List<NotificationIconInfo> = emptyList(), + /** Limit applied to the [visibleIcons]; can be interpreted different based on [limitType]. */ + val iconLimit: Int = visibleIcons.size, + /** How [iconLimit] is applied to [visibleIcons]. */ + val limitType: LimitType = LimitType.MaximumAmount, ) { + // TODO(b/305739416): This can be removed once we are no longer looking up the StatusBarIconView + // instances from outside of the view-binder layer. Ideally, we would just use MaximumAmount, + // and apply it directly to the list of visibleIcons by truncating the list to that amount. + // At the time of this writing, we cannot do that because looking up the SBIV can fail, and so + // we need additional icons to fall-back to. + /** Determines how a limit to the icons is to be applied. */ + enum class LimitType { + /** The [Int] limit is a maximum amount of icons to be displayed. */ + MaximumAmount, + /** + * The [Int] limit is a maximum index into the + * [list of visible icons][NotificationIconsViewData.visibleIcons] to be displayed; any + * icons beyond that index should be omitted. + */ + MaximumIndex, + } + /** The difference between two [NotificationIconsViewData]s. */ data class Diff( /** Icons added in the newer dataset. */ - val added: List<NotificationIconInfo> = emptyList(), + val added: List<String> = emptyList(), /** Icons removed from the older dataset. */ val removed: List<String> = emptyList(), /** @@ -44,7 +61,7 @@ data class NotificationIconsViewData( * same group. A view binder can use this information for special animations for this * specific change. */ - val groupReplacements: Map<String, NotificationIconInfo> = emptyMap(), + val groupReplacements: Map<String, String> = emptyMap(), ) companion object { @@ -56,23 +73,20 @@ data class NotificationIconsViewData( new: NotificationIconsViewData, prev: NotificationIconsViewData ): Diff { - val added: List<NotificationIconInfo> = - new.visibleKeys.filter { - it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey } - } + val prevKeys = prev.visibleIcons.asSequence().map { it.notifKey }.toSet() + val newKeys = new.visibleIcons.asSequence().map { it.notifKey }.toSet() + val added: List<String> = newKeys.mapNotNull { key -> key.takeIf { it !in prevKeys } } val removed: List<NotificationIconInfo> = - prev.visibleKeys.filter { - it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey } - } + prev.visibleIcons.filter { it.notifKey !in newKeys } val groupsToShow: Set<IconGroupInfo> = - new.visibleKeys.asSequence().map { it.groupInfo }.toSet() - val replacements: ArrayMap<String, NotificationIconInfo> = + new.visibleIcons.asSequence().map { it.groupInfo }.toSet() + val replacements: ArrayMap<String, String> = removed .asSequence() .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow } .groupBy { it.groupInfo.groupKey } .mapValuesNotNullTo(ArrayMap()) { (_, vs) -> - vs.takeIf { it.size == 1 }?.get(0) + vs.takeIf { it.size == 1 }?.get(0)?.notifKey } return Diff(added, removed.map { it.notifKey }, replacements) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 538be142b8f2..9f2b0a6bfc1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -32,6 +32,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE @@ -43,9 +44,9 @@ import com.android.systemui.util.time.SystemClock class PeekDisabledSuppressor( private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, - private val logger: NotificationInterruptLogger, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek disabled by global setting") { private var isEnabled = false override fun shouldSuppress(): Boolean = !isEnabled @@ -87,16 +88,13 @@ class PeekDisabledSuppressor( class PulseDisabledSuppressor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val userTracker: UserTracker, -) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") { +) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by user setting") { override fun shouldSuppress(): Boolean = !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId) } class PulseBatterySaverSuppressor(private val batteryController: BatteryController) : - VisualInterruptionCondition( - types = setOf(PULSE), - reason = "pulsing disabled by battery saver" - ) { + VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse disabled by battery saver") { override fun shouldSuppress() = batteryController.isAodPowerSave() } @@ -128,14 +126,14 @@ class PeekDndSuppressor() : } class PeekNotImportantSuppressor() : - VisualInterruptionFilter(types = setOf(PEEK), reason = "not important") { + VisualInterruptionFilter(types = setOf(PEEK), reason = "importance < HIGH") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_HIGH } class PeekDeviceNotInUseSuppressor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController -) : VisualInterruptionCondition(types = setOf(PEEK), reason = "not in use") { +) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") { override fun shouldSuppress() = when { !powerManager.isScreenOn || statusBarStateController.isDreaming -> true @@ -144,7 +142,11 @@ class PeekDeviceNotInUseSuppressor( } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : - VisualInterruptionFilter(types = setOf(PEEK), reason = "old when") { + VisualInterruptionFilter( + types = setOf(PEEK), + reason = "has old `when`", + uiEventId = HUN_SUPPRESSED_OLD_WHEN + ) { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` @@ -165,21 +167,21 @@ class PeekOldWhenSuppressor(private val systemClock: SystemClock) : } class PulseEffectSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "ambient effect suppressed") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "suppressed by DND") { override fun shouldSuppress(entry: NotificationEntry) = entry.shouldSuppressAmbient() } class PulseLockscreenVisibilityPrivateSuppressor() : VisualInterruptionFilter( types = setOf(PULSE), - reason = "notification hidden on lock screen by override" + reason = "hidden by lockscreen visibility override" ) { override fun shouldSuppress(entry: NotificationEntry) = entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE } class PulseLowImportanceSuppressor() : - VisualInterruptionFilter(types = setOf(PULSE), reason = "importance less than DEFAULT") { + VisualInterruptionFilter(types = setOf(PULSE), reason = "importance < DEFAULT") { override fun shouldSuppress(entry: NotificationEntry) = entry.importance < IMPORTANCE_DEFAULT } @@ -198,12 +200,12 @@ class HunJustLaunchedFsiSuppressor() : } class BubbleNotAllowedSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "not allowed") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "cannot bubble") { override fun shouldSuppress(entry: NotificationEntry) = !entry.canBubble() } class BubbleNoMetadataSuppressor() : - VisualInterruptionFilter(types = setOf(BUBBLE), reason = "no bubble metadata") { + VisualInterruptionFilter(types = setOf(BUBBLE), reason = "has no or invalid bubble metadata") { private fun isValidMetadata(metadata: BubbleMetadata?) = metadata != null && (metadata.intent != null || metadata.shortcutId != null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt index 6af25439ecc3..2707ed83b74e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption import android.app.NotificationManager.IMPORTANCE_HIGH import android.os.PowerManager +import com.android.internal.logging.UiEventLogger.UiEventEnum import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -37,6 +38,10 @@ import com.android.systemui.statusbar.notification.interruption.FullScreenIntent import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_ONLY_BY_DND import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -50,19 +55,44 @@ class FullScreenIntentDecisionProvider( val shouldFsi: Boolean val wouldFsiWithoutDnd: Boolean val logReason: String + val shouldLog: Boolean + val isWarning: Boolean + val uiEventId: UiEventEnum? + val eventLogData: EventLogData? } private enum class DecisionImpl( override val shouldFsi: Boolean, override val logReason: String, override val wouldFsiWithoutDnd: Boolean = shouldFsi, - val supersedesDnd: Boolean = false + val supersedesDnd: Boolean = false, + override val shouldLog: Boolean = true, + override val isWarning: Boolean = false, + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null ) : Decision { - NO_FSI_NO_FULL_SCREEN_INTENT(false, "no full-screen intent", supersedesDnd = true), + NO_FSI_NO_FULL_SCREEN_INTENT( + false, + "no full-screen intent", + supersedesDnd = true, + shouldLog = false + ), NO_FSI_SHOW_STICKY_HUN(false, "full-screen intents are disabled", supersedesDnd = true), NO_FSI_NOT_IMPORTANT_ENOUGH(false, "not important enough"), - NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false, "suppressive group alert behavior"), - NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata"), + NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR( + false, + "suppressive group alert behavior", + isWarning = true, + uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, + eventLogData = EventLogData("231322873", "groupAlertBehavior") + ), + NO_FSI_SUPPRESSIVE_BUBBLE_METADATA( + false, + "suppressive bubble metadata", + isWarning = true, + uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, + eventLogData = EventLogData("274759612", "bubbleMetadata") + ), NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"), FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"), FSI_DEVICE_DREAMING(true, "device is dreaming"), @@ -71,7 +101,13 @@ class FullScreenIntentDecisionProvider( FSI_KEYGUARD_OCCLUDED(true, "keyguard is occluded"), FSI_LOCKED_SHADE(true, "locked shade"), FSI_DEVICE_NOT_PROVISIONED(true, "device not provisioned"), - NO_FSI_NO_HUN_OR_KEYGUARD(false, "no HUN or keyguard"), + NO_FSI_NO_HUN_OR_KEYGUARD( + false, + "no HUN or keyguard", + isWarning = true, + uiEventId = FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, + eventLogData = EventLogData("231322873", "no hun or keyguard") + ), NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false), NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index f2ade340fc4f..40453800d17f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -49,6 +49,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.EventLog; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; @@ -81,6 +82,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter private final DeviceProvisionedController mDeviceProvisionedController; private final SystemClock mSystemClock; private final GlobalSettings mGlobalSettings; + private final EventLog mEventLog; @VisibleForTesting protected boolean mUseHeadsUp = false; @@ -129,7 +131,8 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter UserTracker userTracker, DeviceProvisionedController deviceProvisionedController, SystemClock systemClock, - GlobalSettings globalSettings) { + GlobalSettings globalSettings, + EventLog eventLog) { mPowerManager = powerManager; mBatteryController = batteryController; mAmbientDisplayConfiguration = ambientDisplayConfiguration; @@ -144,6 +147,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter mDeviceProvisionedController = deviceProvisionedController; mSystemClock = systemClock; mGlobalSettings = globalSettings; + mEventLog = eventLog; ContentObserver headsUpObserver = new ContentObserver(mainHandler) { @Override public void onChange(boolean selfChange) { @@ -369,7 +373,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // explicitly prevent logging for this (frequent) case return; case NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR: - android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, + mEventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior"); mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, uid, packageName); @@ -377,7 +381,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter decision + ": GroupAlertBehavior will prevent HUN"); return; case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA: - android.util.EventLog.writeEvent(0x534e4554, "274759612", uid, + mEventLog.writeEvent(0x534e4554, "274759612", uid, "bubbleMetadata"); mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid, packageName); @@ -385,7 +389,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter decision + ": BubbleMetadata may prevent HUN"); return; case NO_FSI_NO_HUN_OR_KEYGUARD: - android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, + mEventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard"); mUiEventLogger.log(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, uid, packageName); mLogger.logNoFullscreenWarning(entry, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index d7f0baf4f002..f732e8d74675 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision @@ -62,6 +63,21 @@ class NotificationInterruptStateProviderWrapper( wrapped.removeSuppressor(suppressor) } + override fun addCondition(condition: VisualInterruptionCondition) = notValidInLegacyMode() + + override fun removeCondition(condition: VisualInterruptionCondition) = notValidInLegacyMode() + + override fun addFilter(filter: VisualInterruptionFilter) = notValidInLegacyMode() + + override fun removeFilter(filter: VisualInterruptionFilter) = notValidInLegacyMode() + + private fun notValidInLegacyMode() { + RefactorFlagUtils.assertOnEngBuild( + "This method is only implemented in VisualInterruptionDecisionProviderImpl, " + + "and so should only be called when FLAG_VISUAL_INTERRUPTIONS_REFACTOR is enabled." + ) + } + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt new file mode 100644 index 000000000000..1470b0331359 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt @@ -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.systemui.statusbar.notification.interruption + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING +import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.logKey +import javax.inject.Inject + +class VisualInterruptionDecisionLogger +@Inject +constructor(@NotificationInterruptLog val buffer: LogBuffer) { + fun logHeadsUpFeatureChanged(isEnabled: Boolean) { + buffer.log( + TAG, + INFO, + { bool1 = isEnabled }, + { "HUN feature is now ${if (bool1) "enabled" else "disabled"}" } + ) + } + + fun logWillDismissAll() { + buffer.log(TAG, INFO, {}, { "dismissing all HUNs since feature was disabled" }) + } + + fun logDecision( + type: String, + entry: NotificationEntry, + decision: VisualInterruptionDecisionProvider.Decision + ) { + buffer.log( + TAG, + DEBUG, + { + str1 = type + bool1 = decision.shouldInterrupt + str2 = decision.logReason + str3 = entry.logKey + }, + { + val outcome = if (bool1) "allowed" else "suppressed" + "$str1 $outcome: $str2 (key=$str3)" + } + ) + } + + fun logFullScreenIntentDecision( + entry: NotificationEntry, + decision: FullScreenIntentDecision, + warning: Boolean + ) { + buffer.log( + TAG, + if (warning) WARNING else DEBUG, + { + bool1 = decision.shouldInterrupt + bool2 = decision.wouldInterruptWithoutDnd + str1 = decision.logReason + str2 = entry.logKey + }, + { + val outcome = + when { + bool1 -> "allowed" + bool2 -> "suppressed only by DND" + else -> "suppressed" + } + "FSI $outcome: $str1 (key=$str2)" + } + ) + } +} + +private const val TAG = "VisualInterruptionDecisionProvider" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index da8474e92629..de8863c64873 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.interruption +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.statusbar.notification.collection.NotificationEntry /** @@ -51,33 +52,77 @@ interface VisualInterruptionDecisionProvider { val wouldInterruptWithoutDnd: Boolean } - /** - * Initializes the provider. - * - * Must be called before any method except [addLegacySuppressor]. - */ + /** Initializes the provider. */ fun start() {} /** - * Adds a [component][suppressor] that can suppress visual interruptions. + * Adds a [NotificationInterruptSuppressor] that can suppress visual interruptions. + * + * This method may be called before [start] has been called. * - * This class may call suppressors in any order. + * This class may call suppressors, conditions, and filters in any order. * * @param[suppressor] the suppressor to add */ fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) /** - * Removes a [component][suppressor] that can suppress visual interruptions. + * Removes a previously-added suppressor. + * + * This method may be called before [start] has been called. * * @param[suppressor] the suppressor to remove */ - fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + @VisibleForTesting fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + + /** + * Adds a [VisualInterruptionCondition] that can suppress visual interruptions without examining + * individual notifications. + * + * This method may be called before [start] has been called. + * + * This class may call suppressors, conditions, and filters in any order. + * + * @param[condition] the condition to add + */ + fun addCondition(condition: VisualInterruptionCondition) + + /** + * Removes a previously-added condition. + * + * This method may be called before [start] has been called. + * + * @param[condition] the condition to remove + */ + @VisibleForTesting fun removeCondition(condition: VisualInterruptionCondition) + + /** + * Adds a [VisualInterruptionFilter] that can suppress visual interruptions based on individual + * notifications. + * + * This method may be called before [start] has been called. + * + * This class may call suppressors, conditions, and filters in any order. + * + * @param[filter] the filter to add + */ + fun addFilter(filter: VisualInterruptionFilter) + + /** + * Removes a previously-added filter. + * + * This method may be called before [start] has been called. + * + * @param[filter] the filter to remove + */ + @VisibleForTesting fun removeFilter(filter: VisualInterruptionFilter) /** * Decides whether a [notification][entry] should display as heads-up or not, but does not log * that decision. * + * [start] must be called before this method can be called. + * * @param[entry] the notification that this decision is about * @return the decision to display that notification as heads-up or not */ @@ -93,6 +138,8 @@ interface VisualInterruptionDecisionProvider { * If the device is dozing, the decision will consider whether the notification should "pulse" * (wake the screen up and display the ambient view of the notification). * + * [start] must be called before this method can be called. + * * @see[makeUnloggedHeadsUpDecision] * * @param[entry] the notification that this decision is about @@ -106,6 +153,8 @@ interface VisualInterruptionDecisionProvider { * * The returned decision can be logged by passing it to [logFullScreenIntentDecision]. * + * [start] must be called before this method can be called. + * * @see[makeAndLogHeadsUpDecision] * * @param[entry] the notification that this decision is about @@ -116,6 +165,8 @@ interface VisualInterruptionDecisionProvider { /** * Logs a previous [decision] to launch a full-screen intent or not. * + * [start] must be called before this method can be called. + * * @param[decision] the decision to log */ fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) @@ -123,6 +174,8 @@ interface VisualInterruptionDecisionProvider { /** * Decides whether a [notification][entry] should display as a bubble or not. * + * [start] must be called before this method can be called. + * * @param[entry] the notification that this decision is about * @return the decision to display that notification as a bubble or not */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 9640682a8f39..2b6e1a168b3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -18,13 +18,17 @@ package com.android.systemui.statusbar.notification.interruption import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager +import android.util.Log import com.android.internal.annotations.VisibleForTesting +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEventLogger.UiEventEnum import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE @@ -32,6 +36,7 @@ import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.EventLog import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -42,25 +47,62 @@ constructor( private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val batteryController: BatteryController, deviceProvisionedController: DeviceProvisionedController, + private val eventLog: EventLog, private val globalSettings: GlobalSettings, private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, keyguardStateController: KeyguardStateController, - private val logger: NotificationInterruptLogger, + private val logger: VisualInterruptionDecisionLogger, @Main private val mainHandler: Handler, private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController, private val systemClock: SystemClock, + private val uiEventLogger: UiEventLogger, private val userTracker: UserTracker, ) : VisualInterruptionDecisionProvider { + interface Loggable { + val uiEventId: UiEventEnum? + val eventLogData: EventLogData? + } + private class DecisionImpl( override val shouldInterrupt: Boolean, override val logReason: String ) : Decision + private data class LoggableDecision + private constructor( + val decision: DecisionImpl, + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null + ) : Loggable { + companion object { + val unsuppressed = + LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")) + + fun suppressed(legacySuppressor: NotificationInterruptSuppressor, methodName: String) = + LoggableDecision( + DecisionImpl( + shouldInterrupt = false, + logReason = "${legacySuppressor.name}.$methodName" + ) + ) + + fun suppressed(suppressor: VisualInterruptionSuppressor) = + LoggableDecision( + DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason), + uiEventId = suppressor.uiEventId, + eventLogData = suppressor.eventLogData + ) + } + } + private class FullScreenIntentDecisionImpl( + val entry: NotificationEntry, private val fsiDecision: FullScreenIntentDecisionProvider.Decision - ) : FullScreenIntentDecision { + ) : FullScreenIntentDecision, Loggable { + var hasBeenLogged = false + override val shouldInterrupt get() = fsiDecision.shouldFsi @@ -69,6 +111,18 @@ constructor( override val logReason get() = fsiDecision.logReason + + val shouldLog + get() = fsiDecision.shouldLog + + val isWarning + get() = fsiDecision.isWarning + + override val uiEventId + get() = fsiDecision.uiEventId + + override val eventLogData + get() = fsiDecision.eventLogData } private val fullScreenIntentDecisionProvider = @@ -117,159 +171,144 @@ constructor( legacySuppressors.remove(suppressor) } - fun addCondition(condition: VisualInterruptionCondition) { + override fun addCondition(condition: VisualInterruptionCondition) { conditions.add(condition) condition.start() } @VisibleForTesting - fun removeCondition(condition: VisualInterruptionCondition) { + override fun removeCondition(condition: VisualInterruptionCondition) { conditions.remove(condition) } - fun addFilter(filter: VisualInterruptionFilter) { + override fun addFilter(filter: VisualInterruptionFilter) { filters.add(filter) filter.start() } @VisibleForTesting - fun removeFilter(filter: VisualInterruptionFilter) { + override fun removeFilter(filter: VisualInterruptionFilter) { filters.remove(filter) } override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry) + + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry) + } else { + makeLoggablePeekDecision(entry) + } + .decision } override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision { check(started) - return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) } + + return if (statusBarStateController.isDozing) { + makeLoggablePulseDecision(entry).also { logDecision(PULSE, entry, it) } + } else { + makeLoggablePeekDecision(entry).also { logDecision(PEEK, entry, it) } + } + .decision } + private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PEEK) + ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry) + ?: LoggableDecision.unsuppressed + + private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(PULSE) + ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry) + ?: LoggableDecision.unsuppressed + override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision { check(started) - return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) } + + return makeLoggableBubbleDecision(entry).also { logDecision(BUBBLE, entry, it) }.decision + } + + private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision = + checkConditions(BUBBLE) + ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed + + private fun logDecision( + type: VisualInterruptionType, + entry: NotificationEntry, + loggableDecision: LoggableDecision + ) { + logger.logDecision(type.name, entry, loggableDecision.decision) + logEvents(entry, loggableDecision) } override fun makeUnloggedFullScreenIntentDecision( entry: NotificationEntry ): FullScreenIntentDecision { check(started) - return makeFullScreenIntentDecision(entry) + + val couldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt + val fsiDecision = + fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, couldHeadsUp) + return FullScreenIntentDecisionImpl(entry, fsiDecision) } override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { check(started) - // Not yet implemented. - } - private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl { - if (statusBarStateController.isDozing) { - return makePulseDecision(entry) - } else { - return makePeekDecision(entry) + if (decision !is FullScreenIntentDecisionImpl) { + Log.wtf(TAG, "FSI decision $decision was not created by this class") + return } - } - private fun makePeekDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PEEK)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) + if (decision.hasBeenLogged) { + Log.wtf(TAG, "FSI decision $decision has already been logged") + return } - checkFilters(PEEK, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) - } - checkAwakeHeadsUpSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeHeadsUpInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } - private fun makePulseDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(PULSE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(PULSE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } + decision.hasBeenLogged = true - private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl { - checkConditions(BUBBLE)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkFilters(BUBBLE, entry)?.let { - return DecisionImpl(shouldInterrupt = false, logReason = it.reason) - } - checkSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressInterruptions" - ) - } - checkAwakeSuppressors(entry)?.let { - return DecisionImpl( - shouldInterrupt = false, - logReason = "${it.name}.suppressAwakeInterruptions" - ) + if (!decision.shouldLog) { + return } - return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed") - } - - private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. - } - private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) { - // Not yet implemented. + logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning) + logEvents(decision.entry, decision) } - private fun makeFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision { - val wouldHeadsUp = makeUnloggedHeadsUpDecision(entry).shouldInterrupt - val fsiDecision = - fullScreenIntentDecisionProvider.makeFullScreenIntentDecision(entry, wouldHeadsUp) - return FullScreenIntentDecisionImpl(fsiDecision) + private fun logEvents(entry: NotificationEntry, loggable: Loggable) { + loggable.uiEventId?.let { uiEventLogger.log(it, entry.sbn.uid, entry.sbn.packageName) } + loggable.eventLogData?.let { + eventLog.writeEvent(0x534e4554, it.number, entry.sbn.uid, it.description) + } } - private fun checkSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressInterruptions(entry) } - - private fun checkAwakeSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) } - - private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) = - legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) } - - private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? = - conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() } - - private fun checkFilters( - type: VisualInterruptionType, - entry: NotificationEntry - ): VisualInterruptionFilter? = - filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + private fun checkSuppressInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressInterruptions") } + + private fun checkSuppressAwakeInterruptions(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeInterruptions(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeInterruptions") } + + private fun checkSuppressAwakeHeadsUp(entry: NotificationEntry) = + legacySuppressors + .firstOrNull { it.suppressAwakeHeadsUp(entry) } + ?.let { LoggableDecision.suppressed(it, "suppressAwakeHeadsUp") } + + private fun checkConditions(type: VisualInterruptionType) = + conditions + .firstOrNull { it.types.contains(type) && it.shouldSuppress() } + ?.let { LoggableDecision.suppressed(it) } + + private fun checkFilters(type: VisualInterruptionType, entry: NotificationEntry) = + filters + .firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) } + ?.let { LoggableDecision.suppressed(it) } } private const val TAG = "VisualInterruptionDecisionProviderImpl" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt index 39199df37bd4..2047c62cc5a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.internal.logging.UiEventLogger.UiEventEnum import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData /** * A reason why visual interruptions might be suppressed. @@ -43,6 +44,9 @@ enum class VisualInterruptionType { * @see VisualInterruptionFilter */ sealed interface VisualInterruptionSuppressor { + /** Data to be logged in the EventLog when an interruption is suppressed. */ + data class EventLogData(val number: String, val description: String) + /** The type(s) of interruption that this suppresses. */ val types: Set<VisualInterruptionType> @@ -52,6 +56,9 @@ sealed interface VisualInterruptionSuppressor { /** An optional UiEvent ID to be recorded when this suppresses an interruption. */ val uiEventId: UiEventEnum? + /** Optional data to be logged in the EventLog when this suppresses an interruption. */ + val eventLogData: EventLogData? + /** * Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before * any other methods are called on the suppressor. @@ -63,7 +70,8 @@ sealed interface VisualInterruptionSuppressor { abstract class VisualInterruptionCondition( override val types: Set<VisualInterruptionType>, override val reason: String, - override val uiEventId: UiEventEnum? = null + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null ) : VisualInterruptionSuppressor { /** @return true if these interruptions should be suppressed right now. */ abstract fun shouldSuppress(): Boolean @@ -73,7 +81,8 @@ abstract class VisualInterruptionCondition( abstract class VisualInterruptionFilter( override val types: Set<VisualInterruptionType>, override val reason: String, - override val uiEventId: UiEventEnum? = null + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null ) : VisualInterruptionSuppressor { /** * @param entry the notification to consider suppressing diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 8f1e59d20091..7c8d76208e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.util.DumpUtilsKt; @@ -229,17 +230,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void setBelowSpeedBump(boolean below) { + NotificationIconContainerRefactor.assertInLegacyMode(); super.setBelowSpeedBump(below); if (below != mIsBelowSpeedBump) { mIsBelowSpeedBump = below; updateBackgroundTint(); - onBelowSpeedBumpChanged(); } } - protected void onBelowSpeedBumpChanged() { - } - /** * Sets the tint color of the background */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 6edab4d26d59..49674d603509 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -38,6 +38,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.Roundable; import com.android.systemui.statusbar.notification.RoundableState; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.util.Compile; @@ -400,6 +401,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * @param below true if it is below. */ public void setBelowSpeedBump(boolean below) { + NotificationIconContainerRefactor.assertInLegacyMode(); } public int getPinnedHeadsUpHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 7667e17cac14..5cdead407891 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -24,6 +24,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.row.ui.viewbinder.ActivatableNotificationViewBinder import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel @@ -40,6 +41,7 @@ object NotificationShelfViewBinder { configuration: ConfigurationState, configurationController: ConfigurationController, falsingManager: FalsingManager, + iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, notificationIconAreaController: NotificationIconAreaController, shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -51,6 +53,7 @@ object NotificationShelfViewBinder { viewModel.icons, configuration, configurationController, + iconViewBindingFailureTracker, shelfIconViewStore, ) } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java index 33473a6f6b0a..d0c5c82b50ee 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java @@ -26,6 +26,7 @@ import com.android.app.animation.Interpolators; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; /** * A state of an expandable view @@ -162,7 +163,9 @@ public class ExpandableViewState extends ViewState { this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); // apply below shelf speed bump - expandableView.setBelowSpeedBump(this.belowSpeedBump); + if (!NotificationIconContainerRefactor.isEnabled()) { + expandableView.setBelowSpeedBump(this.belowSpeedBump); + } // apply clipping final float oldClipTopAmount = expandableView.getClipTopAmount(); @@ -217,7 +220,9 @@ public class ExpandableViewState extends ViewState { expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed); // apply below the speed bump - expandableView.setBelowSpeedBump(this.belowSpeedBump); + if (!NotificationIconContainerRefactor.isEnabled()) { + expandableView.setBelowSpeedBump(this.belowSpeedBump); + } // start hiding sensitive animation expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 14ec08f3545f..46488c60fb0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4668,22 +4668,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mClearAllInProgress; } - public boolean isFooterViewNotGone() { - return mFooterView != null - && mFooterView.getVisibility() != View.GONE - && !mFooterView.willBeGone(); - } - - public boolean isFooterViewContentVisible() { - return mFooterView != null && mFooterView.isContentVisible(); - } - - public int getFooterViewHeightWithPadding() { - return mFooterView == null ? 0 : mFooterView.getHeight() - + mPaddingBetweenElements - + mGapHeight; - } - /** * @return the padding after the media header on the lockscreen */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 2cf0c262c528..3e140a45f8b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -858,10 +858,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return row.getVisibility() == View.VISIBLE; } - public boolean isViewAffectedBySwipe(ExpandableView expandableView) { - return mNotificationRoundnessManager.isViewAffectedBySwipe(expandableView); - } - public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { mView.addOnExpandedHeightChangedListener(listener); } @@ -1261,18 +1257,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setPanelFlinging(flinging); } - public boolean isFooterViewNotGone() { - return mView.isFooterViewNotGone(); - } - - public boolean isFooterViewContentVisible() { - return mView.isFooterViewContentVisible(); - } - - public int getFooterViewHeightWithPadding() { - return mView.getFooterViewHeightWithPadding(); - } - /** * Sets whether the bouncer is currently showing. Should only be called from * {@link CentralSurfaces}. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index eb1c17aaca78..c2c5eed6f013 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -73,6 +73,11 @@ constructor( } .distinctUntilChanged() + val isSplitShadeEnabled: Flow<Boolean> = + configurationBasedDimensions + .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade } + .distinctUntilChanged() + /** Top position (without translation) of the shared container. */ fun setTopPosition(top: Float) { _topPosition.value = top diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 6cf56102d65f..a5b87f088578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -27,6 +27,7 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -45,6 +46,7 @@ constructor( private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, + private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val shelfIconViewStore: ShelfNotificationIconViewStore, ) { @@ -68,6 +70,7 @@ constructor( configuration, configurationController, falsingManager, + iconViewBindingFailureTracker, iconAreaController, shelfIconViewStore, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt new file mode 100644 index 000000000000..bcf732214d57 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/StatusBarNotificationViewBinderModule.kt @@ -0,0 +1,23 @@ +/* + * 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.ui.viewbinder + +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker +import dagger.Module + +@Module(includes = [StatusBarIconViewBindingFailureTracker.Module::class]) +object StatusBarNotificationViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 22b9298b629d..60a4606ef0d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; @@ -42,25 +41,23 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.res.R; import com.android.systemui.assist.AssistManager; import com.android.systemui.camera.CameraIntents; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -97,7 +94,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final PowerManager mPowerManager; - private final VibratorHelper mVibratorHelper; private final Optional<Vibrator> mVibratorOptional; private final DisableFlagsLogger mDisableFlagsLogger; private final int mDisplayId; @@ -108,8 +104,6 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final Lazy<CameraLauncher> mCameraLauncherLazy; private final QuickSettingsController mQsController; private final QSHost mQSHost; - private final FeatureFlags mFeatureFlags; - private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @@ -139,15 +133,13 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, PowerManager powerManager, - VibratorHelper vibratorHelper, Optional<Vibrator> vibratorOptional, DisableFlagsLogger disableFlagsLogger, @DisplayId int displayId, Lazy<CameraLauncher> cameraLauncherLazy, UserTracker userTracker, QSHost qsHost, - ActivityStarter activityStarter, - FeatureFlags featureFlags) { + ActivityStarter activityStarter) { mCentralSurfaces = centralSurfaces; mQsController = quickSettingsController; mContext = context; @@ -168,14 +160,12 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mPowerManager = powerManager; - mVibratorHelper = vibratorHelper; mVibratorOptional = vibratorOptional; mDisableFlagsLogger = disableFlagsLogger; mDisplayId = displayId; mCameraLauncherLazy = cameraLauncherLazy; mUserTracker = userTracker; mQSHost = qsHost; - mFeatureFlags = featureFlags; mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation); mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect( @@ -544,12 +534,8 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @VisibleForTesting void vibrateOnNavigationKeyDown() { - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mShadeViewController.performHapticFeedback( - HapticFeedbackConstants.GESTURE_START - ); - } else { - mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); - } + mShadeViewController.performHapticFeedback( + HapticFeedbackConstants.GESTURE_START + ); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 2e5717de1a86..cd7a9eacf552 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -139,6 +139,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder; import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel; import com.android.systemui.navigationbar.NavigationBarController; @@ -159,6 +160,7 @@ import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.qs.QSPanelController; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -583,6 +585,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final InteractionJankMonitor mJankMonitor; + private final SceneContainerFlags mSceneContainerFlags; + /** * Public constructor for CentralSurfaces. * @@ -696,7 +700,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { AlternateBouncerInteractor alternateBouncerInteractor, UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, - ActivityStarter activityStarter + ActivityStarter activityStarter, + SceneContainerFlags sceneContainerFlags ) { mContext = context; mNotificationsController = notificationsController; @@ -793,6 +798,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mUserTracker = userTracker; mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; + mSceneContainerFlags = sceneContainerFlags; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1247,7 +1253,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Set up the quick settings tile panel final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame); - if (container != null) { + if (container != null && !mSceneContainerFlags.isEnabled()) { FragmentHostManager fragmentHostManager = mFragmentService.getFragmentHostManager(container); ExtensionFragmentListener.attachExtensonToFragment( @@ -1449,7 +1455,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mShadeController.onStatusBarTouch(event); } return getNotificationShadeWindowView().onTouchEvent(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index beeee1be401d..495b4e1e14cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -252,7 +252,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } if (NotificationIconContainerRefactor.isEnabled()) { mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey( - newEntry == null ? null : newEntry.getKey()); + newEntry == null ? null : newEntry.getRepresentativeEntry().getKey()); } else { updateIsolatedIconLocation(false /* requireUpdate */); mNotificationIconAreaController.showIconIsolated(newEntry == null ? null diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 3b3d8b6b2109..1f9952a8d4ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -40,7 +40,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -115,7 +115,6 @@ public class LegacyNotificationIconAreaControllerImpl implements private int mAodIconTint; private boolean mAodIconsVisible; private boolean mShowLowPriority = true; - private boolean mIsStatusViewMigrated = false; @VisibleForTesting final NotificationListener.NotificationSettingsListener mSettingsListener = @@ -159,7 +158,6 @@ public class LegacyNotificationIconAreaControllerImpl implements mStatusBarWindowController = statusBarWindowController; mScreenOffAnimationController = screenOffAnimationController; notificationListener.addNotificationSettingsListener(mSettingsListener); - mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW); initializeNotificationAreaViews(context); reloadAodColor(); darkIconDispatcher.addDarkReceiver(this); @@ -551,7 +549,7 @@ public class LegacyNotificationIconAreaControllerImpl implements return; } if (mScreenOffAnimationController.shouldAnimateAodIcons()) { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(-mAodIconAppearTranslation); } mAodIcons.setAlpha(0); @@ -563,14 +561,14 @@ public class LegacyNotificationIconAreaControllerImpl implements .start(); } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } } } private void animateInAodIconTranslation() { - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.animate() .setInterpolator(Interpolators.DECELERATE_QUINT) .translationY(0) @@ -673,7 +671,7 @@ public class LegacyNotificationIconAreaControllerImpl implements } } else { mAodIcons.setAlpha(1.0f); - if (!mIsStatusViewMigrated) { + if (!KeyguardShadeMigrationNssl.isEnabled()) { mAodIcons.setTranslationY(0); } mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java index c20732471cf4..2235035fe5aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java @@ -24,6 +24,8 @@ import android.os.SystemClock; import android.util.MathUtils; import android.util.TimeUtils; +import androidx.annotation.VisibleForTesting; + import com.android.app.animation.Interpolators; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dumpable; @@ -96,13 +98,14 @@ public class LightBarTransitionsController implements Dumpable { private final KeyguardStateController mKeyguardStateController; private final StatusBarStateController mStatusBarStateController; private final CommandQueue mCommandQueue; - private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; + private GestureNavigationSettingsObserver mGestureNavigationSettingsObserver; private boolean mTransitionDeferring; private long mTransitionDeferringStartTime; private long mTransitionDeferringDuration; private boolean mTransitionPending; private boolean mTintChangePending; + private boolean mNavigationButtonsForcedVisible; private float mPendingDarkIntensity; private ValueAnimator mTintAnimator; private float mDarkIntensity; @@ -137,13 +140,16 @@ public class LightBarTransitionsController implements Dumpable { mContext = context; mDisplayId = mContext.getDisplayId(); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( - mHandler, mContext, null); + mHandler, mContext, this::onNavigationSettingsChanged); + mGestureNavigationSettingsObserver.register(); + onNavigationSettingsChanged(); } /** Call to cleanup the LightBarTransitionsController when done with it. */ public void destroy() { mCommandQueue.removeCallback(mCallback); mStatusBarStateController.removeCallback(mCallback); + mGestureNavigationSettingsObserver.unregister(); } public void saveState(Bundle outState) { @@ -199,6 +205,12 @@ public class LightBarTransitionsController implements Dumpable { mTransitionPending = false; } + @VisibleForTesting + void setNavigationSettingsObserver(GestureNavigationSettingsObserver observer) { + mGestureNavigationSettingsObserver = observer; + onNavigationSettingsChanged(); + } + public void setIconsDark(boolean dark, boolean animate) { if (!animate) { setIconTintInternal(dark ? 1.0f : 0.0f); @@ -253,6 +265,28 @@ public class LightBarTransitionsController implements Dumpable { mApplier.applyDarkIntensity(MathUtils.lerp(mDarkIntensity, 0f, mDozeAmount)); } + public void onDozeAmountChanged(float linear, float eased) { + mDozeAmount = eased; + dispatchDark(); + } + + /** + * Called when the navigation settings change. + */ + private void onNavigationSettingsChanged() { + mNavigationButtonsForcedVisible = + mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); + } + + /** + * Return whether to use the tint calculated in this class for nav icons. + */ + public boolean supportsIconTintForNavMode(int navigationMode) { + // In gesture mode, we already do region sampling to update tint based on content beneath. + return !QuickStepContract.isGesturalMode(navigationMode) + || mNavigationButtonsForcedVisible; + } + @Override public void dump(PrintWriter pw, String[] args) { pw.print(" mTransitionDeferring="); pw.print(mTransitionDeferring); @@ -271,20 +305,7 @@ public class LightBarTransitionsController implements Dumpable { pw.print(" mPendingDarkIntensity="); pw.print(mPendingDarkIntensity); pw.print(" mDarkIntensity="); pw.print(mDarkIntensity); pw.print(" mNextDarkIntensity="); pw.println(mNextDarkIntensity); - } - - public void onDozeAmountChanged(float linear, float eased) { - mDozeAmount = eased; - dispatchDark(); - } - - /** - * Return whether to use the tint calculated in this class for nav icons. - */ - public boolean supportsIconTintForNavMode(int navigationMode) { - // In gesture mode, we already do region sampling to update tint based on content beneath. - return !QuickStepContract.isGesturalMode(navigationMode) - || mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); + pw.print(" mAreNavigationButtonForcedVisible="); pw.println(mNavigationButtonsForcedVisible); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 9e5fd959f2d0..00e78a49ba19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -141,8 +141,10 @@ public class NotificationIconContainer extends ViewGroup { private int mMaxStaticIcons; private boolean mDozing; private boolean mOnLockScreen; - private boolean mOverrideIconColor; + private int mSpeedBumpIndex = -1; + private int mMaxIcons = Integer.MAX_VALUE; + private boolean mOverrideIconColor; private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); private int mDotPadding; @@ -153,7 +155,6 @@ public class NotificationIconContainer extends ViewGroup { private boolean mChangingViewPositions; private int mAddAnimationStartIndex = -1; private int mCannedAnimationStartIndex = -1; - private int mSpeedBumpIndex = -1; private int mIconSize; private boolean mDisallowNextAnimation; private boolean mAnimationsEnabled = true; @@ -170,6 +171,7 @@ public class NotificationIconContainer extends ViewGroup { private View mIsolatedIconForAnimation; private int mThemedTextColorPrimary; private Runnable mIsolatedIconAnimationEndRunnable; + private boolean mUseIncreasedIconScale; public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); @@ -436,18 +438,24 @@ public class NotificationIconContainer extends ViewGroup { if (numIcons == 0) { return 0f; } - final float contentWidth = - mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1); - return getActualPaddingStart() - + contentWidth - + getActualPaddingEnd(); + final float contentWidth; + if (NotificationIconContainerRefactor.isEnabled()) { + contentWidth = mIconSize * numIcons; + } else { + contentWidth = mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1); + } + return getActualPaddingStart() + contentWidth + getActualPaddingEnd(); } @VisibleForTesting boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount, int maxVisibleIcons) { - return speedBumpIndex != -1 && i >= speedBumpIndex - && iconAppearAmount > 0.0f || i >= maxVisibleIcons; + if (NotificationIconContainerRefactor.isEnabled()) { + return i >= maxVisibleIcons && iconAppearAmount > 0.0f; + } else { + return speedBumpIndex != -1 && i >= speedBumpIndex + && iconAppearAmount > 0.0f || i >= maxVisibleIcons; + } } @VisibleForTesting @@ -502,9 +510,8 @@ public class NotificationIconContainer extends ViewGroup { firstOverflowIndex = i; mVisualOverflowStart = translationX; } - final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView - ? ((StatusBarIconView) view).getIconScaleIncreased() - : 1f; + + final float drawingScale = getDrawingScale(view); translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; } mIsShowingOverflowDot = false; @@ -554,9 +561,26 @@ public class NotificationIconContainer extends ViewGroup { } } + private float getDrawingScale(View view) { + final boolean useIncreasedScale = NotificationIconContainerRefactor.isEnabled() + ? mUseIncreasedIconScale + : mOnLockScreen; + return useIncreasedScale && view instanceof StatusBarIconView + ? ((StatusBarIconView) view).getIconScaleIncreased() + : 1f; + } + + public void setUseIncreasedIconScale(boolean useIncreasedIconScale) { + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; + mUseIncreasedIconScale = useIncreasedIconScale; + } + private int getMaxVisibleIcons(int childCount) { - return mOnLockScreen ? mMaxIconsOnAod : - mIsStaticLayout ? mMaxStaticIcons : childCount; + if (NotificationIconContainerRefactor.isEnabled()) { + return mMaxIcons; + } else { + return mOnLockScreen ? mMaxIconsOnAod : mIsStaticLayout ? mMaxStaticIcons : childCount; + } } private float getLayoutEnd() { @@ -673,9 +697,15 @@ public class NotificationIconContainer extends ViewGroup { } public void setSpeedBumpIndex(int speedBumpIndex) { + NotificationIconContainerRefactor.assertInLegacyMode(); mSpeedBumpIndex = speedBumpIndex; } + public void setMaxIconsAmount(int maxIcons) { + if (NotificationIconContainerRefactor.isUnexpectedlyInLegacyMode()) return; + mMaxIcons = maxIcons; + } + public int getIconSize() { return mIconSize; } @@ -740,6 +770,7 @@ public class NotificationIconContainer extends ViewGroup { * configured to. Depending on these values, the layout of the AOD icons change. */ public void setOnLockScreen(boolean onLockScreen) { + NotificationIconContainerRefactor.assertInLegacyMode(); mOnLockScreen = onLockScreen; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index fb586eac5ab7..1a17e7c28410 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -20,6 +20,7 @@ import com.android.app.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim @@ -34,8 +35,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.app.tracing.TraceUtils import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags /** * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely @@ -68,7 +67,6 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, private val handler: Handler = Handler(), - private val featureFlags: FeatureFlags, ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -288,7 +286,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // up, with unpredictable consequences. if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) && shouldAnimateInKeyguard) { - if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { + if (!KeyguardShadeMigrationNssl.isEnabled) { // Tracking this state should no longer be relevant, as the isInteractive // check covers it aodUiAnimationPlaying = true diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 3921e69501d2..7adc08ca00c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableSta import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; @@ -217,6 +218,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mWaitingForWindowStateChangeAfterCameraLaunch = false; mTransitionFromLockscreenToDreamStarted = false; }; + private final StatusBarIconViewBindingFailureTracker mIconViewBindingFailureTracker; @Inject public CollapsedStatusBarFragment( @@ -235,6 +237,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, StatusBarStateController statusBarStateController, + StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, CommandQueue commandQueue, CarrierConfigTracker carrierConfigTracker, CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, @@ -264,6 +267,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mKeyguardStateController = keyguardStateController; mShadeViewController = shadeViewController; mStatusBarStateController = statusBarStateController; + mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mCommandQueue = commandQueue; mCarrierConfigTracker = carrierConfigTracker; mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; @@ -471,6 +475,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarIconsViewModel, mConfigurationState, mConfigurationController, + mIconViewBindingFailureTracker, mStatusBarIconViewStore); } else { mNotificationIconAreaInner = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt index a052008d4832..a052008d4832 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt index 22d048343bc9..a2f5701d7eca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt @@ -16,15 +16,41 @@ package com.android.systemui.statusbar.pipeline.mobile.util +import android.annotation.SuppressLint +import android.content.Context +import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext interface SubscriptionManagerProxy { fun getDefaultDataSubscriptionId(): Int + fun isValidSubscriptionId(subId: Int): Boolean + suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? } /** Injectable proxy class for [SubscriptionManager]'s static methods */ -class SubscriptionManagerProxyImpl @Inject constructor() : SubscriptionManagerProxy { +class SubscriptionManagerProxyImpl +@Inject +constructor( + @Application private val applicationContext: Context, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val subscriptionManager: SubscriptionManager, +) : SubscriptionManagerProxy { /** The system default data subscription id, or INVALID_SUBSCRIPTION_ID on error */ override fun getDefaultDataSubscriptionId() = SubscriptionManager.getDefaultDataSubscriptionId() + + override fun isValidSubscriptionId(subId: Int): Boolean { + return SubscriptionManager.isValidSubscriptionId(subId) + } + + @SuppressLint("MissingPermission") + override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? { + return withContext(backgroundDispatcher) { + subscriptionManager.getActiveSubscriptionInfo(subId) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index b614b6d0547d..78954dea27ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -35,13 +35,12 @@ import com.android.keyguard.KeyguardConstants; import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R; import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.user.UserSwitchDialogController; +import com.android.systemui.res.R; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -149,7 +148,6 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, UserSwitchDialogController userSwitchDialogController, - FeatureFlags featureFlags, UiEventLogger uiEventLogger) { super(view); if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); @@ -163,7 +161,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mUserSwitchDialogController = userSwitchDialogController; mUiEventLogger = uiEventLogger; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index dfe26865f978..770f441799b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -39,7 +39,6 @@ import com.android.keyguard.KeyguardVisibilityHelper; import com.android.keyguard.dagger.KeyguardUserSwitcherScope; import com.android.settingslib.drawable.CircleFramedDrawable; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; @@ -161,7 +160,6 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, - FeatureFlags featureFlags, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController) { super(keyguardUserSwitcherView); @@ -177,7 +175,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ false, - featureFlags, /* logBuffer= */ null); + /* logBuffer= */ null); mBackground = new KeyguardUserSwitcherScrim(context); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 818ba9512e15..3304b9827fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -71,6 +71,7 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository; import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl; import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepositoryModule; +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepositoryModule; import dagger.Binds; import dagger.Module; @@ -81,7 +82,7 @@ import java.util.concurrent.Executor; import javax.inject.Named; /** Dagger Module for code in the statusbar.policy package. */ -@Module(includes = { DeviceProvisioningRepositoryModule.class }) +@Module(includes = { DeviceProvisioningRepositoryModule.class, ZenModeRepositoryModule.class }) public interface StatusBarPolicyModule { String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt new file mode 100644 index 000000000000..94ab58ae5a3d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepository.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.statusbar.policy.ZenModeController +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** + * A repository that holds information about the status and configuration of Zen Mode (or Do Not + * Disturb/DND Mode). + */ +interface ZenModeRepository { + val zenMode: Flow<Int> + val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> +} + +class ZenModeRepositoryImpl +@Inject +constructor( + private val zenModeController: ZenModeController, +) : ZenModeRepository { + // TODO(b/308591859): ZenModeController should use flows instead of callbacks. The + // conflatedCallbackFlows here should be replaced eventually, see: + // https://docs.google.com/document/d/1gAiuYupwUAFdbxkDXa29A4aFNu7XoCd7sCIk31WTnHU/edit?resourcekey=0-J4ZBiUhLhhQnNobAcI2vIw + + override val zenMode: Flow<Int> = conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onZenChanged(zen: Int) { + trySend(zen) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.zen) + + awaitClose { zenModeController.removeCallback(callback) } + } + + override val consolidatedNotificationPolicy: Flow<NotificationManager.Policy?> = + conflatedCallbackFlow { + val callback = + object : ZenModeController.Callback { + override fun onConsolidatedPolicyChanged(policy: NotificationManager.Policy?) { + trySend(policy) + } + } + zenModeController.addCallback(callback) + trySend(zenModeController.consolidatedPolicy) + + awaitClose { zenModeController.removeCallback(callback) } + } +} + +@Module +interface ZenModeRepositoryModule { + @Binds fun bindImpl(impl: ZenModeRepositoryImpl): ZenModeRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt new file mode 100644 index 000000000000..ae31851cb8f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.provider.Settings +import com.android.systemui.statusbar.policy.data.repository.ZenModeRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** + * An interactor that performs business logic related to the status and configuration of Zen Mode + * (or Do Not Disturb/DND Mode). + */ +class ZenModeInteractor @Inject constructor(repository: ZenModeRepository) { + val isZenModeEnabled: Flow<Boolean> = + repository.zenMode + .map { + when (it) { + Settings.Global.ZEN_MODE_ALARMS -> true + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> true + Settings.Global.ZEN_MODE_OFF -> false + else -> false + } + } + .distinctUntilChanged() + + val areNotificationsHiddenInShade: Flow<Boolean> = + combine(isZenModeEnabled, repository.consolidatedNotificationPolicy) { dndEnabled, policy -> + if (!dndEnabled) { + false + } else { + val showInNotificationList = policy?.showInNotificationList() ?: true + !showInNotificationList + } + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt new file mode 100644 index 000000000000..4cbdd6fd6488 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/StatusBarViewBinderModule.kt @@ -0,0 +1,22 @@ +/* + * 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.ui.binder + +import com.android.systemui.statusbar.notification.ui.viewbinder.StatusBarNotificationViewBinderModule +import dagger.Module + +@Module(includes = [StatusBarNotificationViewBinderModule::class]) object StatusBarViewBinderModule diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index fc414b66b042..2f2c4b0530fb 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators import com.android.internal.widget.CachingIconView import com.android.systemui.Gefingerpoken -import com.android.systemui.res.R import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text.Companion.loadText @@ -48,9 +47,8 @@ import com.android.systemui.common.ui.binder.TintedIconViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewDisplayController @@ -96,7 +94,6 @@ constructor( wakeLockBuilder: WakeLock.Builder, systemClock: SystemClock, tempViewUiEventLogger: TemporaryViewUiEventLogger, - private val featureFlags: FeatureFlags, ) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>( context, @@ -234,18 +231,14 @@ constructor( maybeGetAccessibilityFocus(newInfo, currentView) // ---- Haptics ---- - if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - vibratorHelper.performHapticFeedback(parent, newInfo.vibrationConstant) - } else { - newInfo.vibrationEffect?.let { - vibratorHelper.vibrate( - Process.myUid(), - context.getApplicationContext().getPackageName(), - it, - newInfo.windowTitle, - VIBRATION_ATTRIBUTES, - ) - } + newInfo.vibrationEffect?.let { + vibratorHelper.vibrate( + Process.myUid(), + context.getApplicationContext().getPackageName(), + it, + newInfo.windowTitle, + VIBRATION_ATTRIBUTES, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 4449f8d06f15..7475388e80c2 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -17,13 +17,12 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect -import android.view.HapticFeedbackConstants import android.view.View import androidx.annotation.AttrRes import com.android.internal.logging.InstanceId -import com.android.systemui.res.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.res.R import com.android.systemui.temporarydisplay.TemporaryViewInfo import com.android.systemui.temporarydisplay.ViewPriority @@ -43,7 +42,6 @@ data class ChipbarInfo( val text: Text, val endItem: ChipbarEndItem?, val vibrationEffect: VibrationEffect? = null, - val vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS, val allowSwipeToDismiss: Boolean = false, override val windowTitle: String, override val wakeReason: String, diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLog.kt b/packages/SystemUI/src/com/android/systemui/util/EventLog.kt new file mode 100644 index 000000000000..dc794cf66a8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/EventLog.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +/** + * Testable wrapper around {@link android.util.EventLog}. + * + * Dagger can inject this wrapper into your classes. The implementation just proxies calls to the + * real EventLog. + * + * In tests, pass an instance of FakeEventLog, which allows you to examine the values passed to the + * various methods below. + */ +interface EventLog { + /** @see android.util.EventLog.writeEvent */ + fun writeEvent(tag: Int, value: Int): Int + + /** @see android.util.EventLog.writeEvent */ + fun writeEvent(tag: Int, value: Long): Int + + /** @see android.util.EventLog.writeEvent */ + fun writeEvent(tag: Int, value: Float): Int + + /** @see android.util.EventLog.writeEvent */ + fun writeEvent(tag: Int, value: String): Int + + /** @see android.util.EventLog.writeEvent */ + fun writeEvent(tag: Int, vararg values: Any): Int +} diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt b/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt new file mode 100644 index 000000000000..6fb1adc9382d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/EventLogImpl.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import javax.inject.Inject + +/** Default implementation of [com.android.systemui.util.EventLog]. */ +class EventLogImpl @Inject constructor() : EventLog { + override fun writeEvent(tag: Int, value: Int): Int = + android.util.EventLog.writeEvent(tag, value) + + override fun writeEvent(tag: Int, value: Long): Int = + android.util.EventLog.writeEvent(tag, value) + + override fun writeEvent(tag: Int, value: Float): Int = + android.util.EventLog.writeEvent(tag, value) + + override fun writeEvent(tag: Int, value: String): Int = + android.util.EventLog.writeEvent(tag, value) + + override fun writeEvent(tag: Int, vararg values: Any): Int = + android.util.EventLog.writeEvent(tag, *values) +} diff --git a/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt new file mode 100644 index 000000000000..ca0876ca32cc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/EventLogModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module + +@Module +interface EventLogModule { + @SysUISingleton @Binds fun bindEventLog(eventLogImpl: EventLogImpl?): EventLog? +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 84d2b3761fcc..404621d1fe81 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,7 +34,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -83,7 +82,6 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; import android.view.Gravity; -import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; import android.view.View.AccessibilityDelegate; @@ -120,7 +118,6 @@ import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.Prefs; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -304,7 +301,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final DevicePostureController mDevicePostureController; private @DevicePostureController.DevicePostureInt int mDevicePosture; private int mOrientation; - private final FeatureFlags mFeatureFlags; private final Lazy<SecureSettings> mSecureSettings; private int mDialogTimeoutMillis; @@ -323,9 +319,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DevicePostureController devicePostureController, Looper looper, DumpManager dumpManager, - FeatureFlags featureFlags, Lazy<SecureSettings> secureSettings) { - mFeatureFlags = featureFlags; mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); mHandler = new H(looper); @@ -1373,14 +1367,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void provideTouchFeedbackH(int newRingerMode) { VibrationEffect effect = null; - int hapticConstant = HapticFeedbackConstants.NO_HAPTICS; switch (newRingerMode) { case RINGER_MODE_NORMAL: mController.scheduleTouchFeedback(); break; case RINGER_MODE_SILENT: effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - hapticConstant = HapticFeedbackConstants.TOGGLE_OFF; break; case RINGER_MODE_VIBRATE: // Feedback handled by onStateChange, for feedback both when user toggles @@ -1388,11 +1380,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, break; default: effect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); - hapticConstant = HapticFeedbackConstants.TOGGLE_ON; } - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - mDialogView.performHapticFeedback(hapticConstant); - } else if (effect != null) { + if (effect != null) { mController.vibrate(effect); } } @@ -1820,22 +1809,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, && mState.ringerModeInternal != -1 && mState.ringerModeInternal != state.ringerModeInternal && state.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { - - if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { - if (mShowing) { - // The dialog view is responsible for triggering haptics in the oneway API - mDialogView.performHapticFeedback(HapticFeedbackConstants.TOGGLE_ON); - } - /* - TODO(b/290642122): If the dialog is not showing, we have the case where haptics is - enabled by dragging the volume slider of Settings to a value of 0. This must be - handled by view Slices in Settings whilst using the performHapticFeedback API. - */ - - } else { - // Old behavior only active if the oneway API is not used. - mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); - } + mController.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)); } mState = state; mDynamic.clear(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index e3b3c21d5d0d..53217d481599 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -22,7 +22,6 @@ import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialog; @@ -65,7 +64,6 @@ public interface VolumeModule { CsdWarningDialog.Factory csdFactory, DevicePostureController devicePostureController, DumpManager dumpManager, - FeatureFlags featureFlags, Lazy<SecureSettings> secureSettings) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -82,7 +80,6 @@ public interface VolumeModule { devicePostureController, Looper.getMainLooper(), dumpManager, - featureFlags, secureSettings); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt index 97e43ad91f53..d8e7cd642ad9 100644 --- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt +++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt @@ -26,11 +26,19 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.BaseShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorSceneContainerImpl import dagger.Binds import dagger.Module import dagger.Provides +import javax.inject.Provider import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlinx.coroutines.CoroutineStart @@ -56,6 +64,7 @@ interface SysUITestModule { @Binds @Application fun bindAppResources(resources: Resources): Resources @Binds @Main fun bindMainResources(resources: Resources): Resources @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher + @Binds @SysUISingleton fun bindsShadeInteractor(sii: ShadeInteractorImpl): ShadeInteractor companion object { @Provides @@ -72,6 +81,19 @@ interface SysUITestModule { @Provides fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher = test.fakeBroadcastDispatcher + + @Provides + fun provideBaseShadeInteractor( + sceneContainerFlags: SceneContainerFlags, + sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>, + sceneContainerOff: Provider<ShadeInteractorLegacyImpl> + ): BaseShadeInteractor { + return if (sceneContainerFlags.isEnabled()) { + sceneContainerOn.get() + } else { + sceneContainerOff.get() + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 4d8768f5e9e0..2e9b7e84344b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -127,7 +127,6 @@ class ClockEventControllerTest : SysuiTestCase() { withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) set(Flags.FACE_AUTH_REFACTOR, false) } underTest = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index a38ba00d898f..6a08eeac8108 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -21,7 +21,6 @@ import static android.view.View.INVISIBLE; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; import static com.android.systemui.flags.Flags.MIGRATE_CLOCKS_TO_BLUEPRINT; -import static com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; @@ -60,6 +59,7 @@ import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; @@ -181,7 +181,6 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mFakeFeatureFlags = new FakeFeatureFlags(); mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false); mFakeFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFakeFeatureFlags.set(MIGRATE_KEYGUARD_STATUS_VIEW, false); mFakeFeatureFlags.set(MIGRATE_CLOCKS_TO_BLUEPRINT, false); mController = new KeyguardClockSwitchController( mView, @@ -192,6 +191,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mSmartspaceController, mock(ConfigurationController.class), mock(ScreenOffAnimationController.class), + mock(StatusBarIconViewBindingFailureTracker.class), mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 22c75d85d4a1..146715d26b7d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -30,7 +30,6 @@ import com.android.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.keyguard.domain.interactor.KeyguardTransitionInteractor; @@ -60,7 +59,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected ScreenOffAnimationController mScreenOffAnimationController; @Mock protected KeyguardLogger mKeyguardLogger; @Mock protected KeyguardStatusViewController mControllerMock; - @Mock protected FeatureFlags mFeatureFlags; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -91,7 +89,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, deps.getKeyguardInteractor(), mKeyguardTransitionInteractor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 776799ec054e..2b41e08065d1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -3436,7 +3436,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus); when(mUsbPort.supportsComplianceWarnings()).thenReturn(true); when(mUsbPortStatus.isConnected()).thenReturn(true); - when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1}); + when(mUsbPortStatus.getComplianceWarnings()) + .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY}); } private Context getSpyContext() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java index 67d6aa8e98cf..d8799e16ebdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java @@ -54,7 +54,7 @@ import org.mockito.MockitoAnnotations; /** * Tests for {@link android.view.accessibility.IWindowMagnificationConnection} retrieved from - * {@link WindowMagnification} + * {@link Magnification} */ @SmallTest @RunWith(AndroidTestingRunner.class) @@ -86,7 +86,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { private AccessibilityLogger mA11yLogger; private IWindowMagnificationConnection mIWindowMagnificationConnection; - private WindowMagnification mWindowMagnification; + private Magnification mMagnification; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @Before @@ -98,16 +98,16 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { return null; }).when(mAccessibilityManager).setWindowMagnificationConnection( any(IWindowMagnificationConnection.class)); - mWindowMagnification = new WindowMagnification(getContext(), + mMagnification = new Magnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger); - mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( + mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( mContext.getSystemService(DisplayManager.class)); - mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( + mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( mContext.getSystemService(DisplayManager.class)); - mWindowMagnification.requestWindowMagnificationConnection(true); + mMagnification.requestWindowMagnificationConnection(true); assertNotNull(mIWindowMagnificationConnection); mIWindowMagnificationConnection.setConnectionCallback(mConnectionCallback); } @@ -161,7 +161,7 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { @Test public void showMagnificationButton() throws RemoteException { // magnification settings panel should not be showing - assertFalse(mWindowMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); mIWindowMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); @@ -195,8 +195,8 @@ public class IWindowMagnificationConnectionTest extends SysuiTestCase { testUserId, TEST_DISPLAY, testScale); waitForIdleSync(); - assertTrue(mWindowMagnification.mUsersScales.contains(testUserId)); - assertEquals(mWindowMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY), + assertTrue(mMagnification.mUsersScales.contains(testUserId)); + assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY), (Float) testScale); verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index d7b6602c2f5c..c972febf2c7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -65,7 +65,7 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class WindowMagnificationTest extends SysuiTestCase { +public class MagnificationTest extends SysuiTestCase { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock @@ -82,7 +82,7 @@ public class WindowMagnificationTest extends SysuiTestCase { private SecureSettings mSecureSettings; private CommandQueue mCommandQueue; - private WindowMagnification mWindowMagnification; + private Magnification mMagnification; private OverviewProxyListener mOverviewProxyListener; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); @@ -107,12 +107,12 @@ public class WindowMagnificationTest extends SysuiTestCase { when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); doAnswer(invocation -> { - mWindowMagnification.mMagnificationSettingsControllerCallback + mMagnification.mMagnificationSettingsControllerCallback .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true); return null; }).when(mMagnificationSettingsController).toggleSettingsPanelVisibility(); doAnswer(invocation -> { - mWindowMagnification.mMagnificationSettingsControllerCallback + mMagnification.mMagnificationSettingsControllerCallback .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false); return null; }).when(mMagnificationSettingsController).closeMagnificationSettings(); @@ -120,15 +120,15 @@ public class WindowMagnificationTest extends SysuiTestCase { when(mWindowMagnificationController.isActivated()).thenReturn(true); mCommandQueue = new CommandQueue(getContext(), mDisplayTracker); - mWindowMagnification = new WindowMagnification(getContext(), + mMagnification = new Magnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger); - mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( + mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( mContext.getSystemService(DisplayManager.class), mWindowMagnificationController); - mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( + mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier( mContext.getSystemService(DisplayManager.class), mMagnificationSettingsController); - mWindowMagnification.start(); + mMagnification.start(); final ArgumentCaptor<OverviewProxyListener> listenerArgumentCaptor = ArgumentCaptor.forClass(OverviewProxyListener.class); @@ -156,7 +156,7 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mWindowMagnifierCallback + mMagnification.mWindowMagnifierCallback .onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds); verify(mConnectionCallback).onWindowMagnifierBoundsChanged(TEST_DISPLAY, testBounds); @@ -169,7 +169,7 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mWindowMagnifierCallback + mMagnification.mWindowMagnifierCallback .onPerformScaleAction(TEST_DISPLAY, newScale, updatePersistence); verify(mConnectionCallback).onPerformScaleAction( @@ -181,7 +181,7 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mWindowMagnifierCallback + mMagnification.mWindowMagnifierCallback .onAccessibilityActionPerformed(TEST_DISPLAY); verify(mConnectionCallback).onAccessibilityActionPerformed(TEST_DISPLAY); @@ -192,14 +192,14 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY); + mMagnification.mWindowMagnifierCallback.onMove(TEST_DISPLAY); verify(mConnectionCallback).onMove(TEST_DISPLAY); } @Test public void onClickSettingsButton_enabled_showPanelForWindowMode() { - mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY); + mMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY); waitForIdleSync(); verify(mMagnificationSettingsController).toggleSettingsPanelVisibility(); @@ -212,7 +212,7 @@ public class WindowMagnificationTest extends SysuiTestCase { @Test public void onSetMagnifierSize_delegateToMagnifier() { final @MagnificationSize int index = MagnificationSize.SMALL; - mWindowMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize( + mMagnification.mMagnificationSettingsControllerCallback.onSetMagnifierSize( TEST_DISPLAY, index); waitForIdleSync(); @@ -225,7 +225,7 @@ public class WindowMagnificationTest extends SysuiTestCase { @Test public void onSetDiagonalScrolling_delegateToMagnifier() { - mWindowMagnification.mMagnificationSettingsControllerCallback.onSetDiagonalScrolling( + mMagnification.mMagnificationSettingsControllerCallback.onSetDiagonalScrolling( TEST_DISPLAY, /* enable= */ true); waitForIdleSync(); @@ -235,7 +235,7 @@ public class WindowMagnificationTest extends SysuiTestCase { @Test public void onEditMagnifierSizeMode_windowActivated_delegateToMagnifier() { when(mWindowMagnificationController.isActivated()).thenReturn(true); - mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode( + mMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode( TEST_DISPLAY, /* enable= */ true); waitForIdleSync(); @@ -243,7 +243,7 @@ public class WindowMagnificationTest extends SysuiTestCase { verify(mA11yLogger).log( eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED)); - mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode( + mMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode( TEST_DISPLAY, /* enable= */ false); waitForIdleSync(); verify(mA11yLogger).log( @@ -258,7 +258,7 @@ public class WindowMagnificationTest extends SysuiTestCase { waitForIdleSync(); final float scale = 3.0f; final boolean updatePersistence = false; - mWindowMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale( + mMagnification.mMagnificationSettingsControllerCallback.onMagnifierScale( TEST_DISPLAY, scale, updatePersistence); verify(mConnectionCallback).onPerformScaleAction( @@ -274,7 +274,7 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( + mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); waitForIdleSync(); @@ -292,7 +292,7 @@ public class WindowMagnificationTest extends SysuiTestCase { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); - mWindowMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( + mMagnification.mMagnificationSettingsControllerCallback.onModeSwitch( TEST_DISPLAY, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); waitForIdleSync(); @@ -305,7 +305,7 @@ public class WindowMagnificationTest extends SysuiTestCase { public void onSettingsPanelVisibilityChanged_windowActivated_delegateToMagnifier() { when(mWindowMagnificationController.isActivated()).thenReturn(true); final boolean shown = false; - mWindowMagnification.mMagnificationSettingsControllerCallback + mMagnification.mMagnificationSettingsControllerCallback .onSettingsPanelVisibilityChanged(TEST_DISPLAY, shown); waitForIdleSync(); @@ -325,9 +325,9 @@ public class WindowMagnificationTest extends SysuiTestCase { @Test public void overviewProxyIsConnected_controllerIsAvailable_updateSysUiStateFlag() { final WindowMagnificationController mController = mock(WindowMagnificationController.class); - mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( + mMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier( mContext.getSystemService(DisplayManager.class), mController); - mWindowMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY); + mMagnification.mMagnificationControllerSupplier.get(TEST_DISPLAY); mOverviewProxyListener.onConnectionChanged(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index ae2ec2ca1fe9..64ddbc7828ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -29,7 +29,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -51,10 +54,12 @@ class AuthenticationRepositoryTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode> + @Mock private lateinit var tableLogger: TableLogBuffer private val testUtils = SceneTestUtils(this) private val testScope = testUtils.testScope private val userRepository = FakeUserRepository() + private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var underTest: AuthenticationRepository @@ -67,6 +72,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() { userRepository.setUserInfos(USER_INFOS) runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode } + mobileConnectionsRepository = + FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger) underTest = AuthenticationRepositoryImpl( @@ -76,6 +83,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { userRepository = userRepository, lockPatternUtils = lockPatternUtils, broadcastDispatcher = fakeBroadcastDispatcher, + mobileConnectionsRepository = mobileConnectionsRepository, ) } @@ -97,6 +105,11 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) assertThat(underTest.getAuthenticationMethod()) .isEqualTo(AuthenticationMethodModel.None) + + currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin + mobileConnectionsRepository.isAnySimSecure.value = true + assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim) + assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim) } @Test @@ -143,6 +156,22 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) } + @Test + fun isPinEnhancedPrivacyEnabled() = + testScope.runTest { + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) + .thenReturn(false) + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id)) + .thenReturn(true) + + val values by collectValues(underTest.isPinEnhancedPrivacyEnabled) + assertThat(values.first()).isTrue() + assertThat(values.last()).isFalse() + + userRepository.setSelectedUserInfo(USER_INFOS[1]) + assertThat(values.last()).isTrue() + } + private fun setSecurityModeAndDispatchBroadcast( securityMode: KeyguardSecurityModel.SecurityMode, ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 7439db29b513..56d3d260d196 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -455,4 +455,22 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(hintedPinLength).isNull() } + + @Test + fun authenticate_withTooShortPassword() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + assertThat( + underTest.authenticate( + buildList { + repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + add("$time") + } + } + ) + ) + .isEqualTo(AuthenticationResult.SKIPPED) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index c5f16aa97f18..5f0d4d428322 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -43,7 +43,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -68,7 +68,6 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import javax.inject.Provider import org.mockito.Mockito.`when` as whenever private const val REQUEST_ID = 2L @@ -111,11 +110,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor - @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate - @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels> + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -164,7 +162,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { alternateBouncerInteractor, isDebuggable, udfpsKeyguardAccessibilityDelegate, - udfpsKeyguardViewModels, + keyguardTransitionInteractor, mSelectedUserInteractor, ) block() 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 675ca639493e..c8c400de5740 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -86,6 +86,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; @@ -237,6 +238,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ViewRootImpl mViewRootImpl; @Mock private FpsUnlockTracker mFpsUnlockTracker; + @Mock + private KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Before public void setUp() { @@ -329,7 +332,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels, mSelectedUserInteractor, - mFpsUnlockTracker + mFpsUnlockTracker, + mKeyguardTransitionInteractor ); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java index 2c4e1362bed3..2ea803c6aa8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java @@ -31,6 +31,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -71,6 +72,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; protected @Mock SelectedUserInteractor mSelectedUserInteractor; + protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor; protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @@ -141,11 +143,11 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { mDialogManager, mUdfpsController, mActivityLaunchAnimator, - mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsKeyguardAccessibilityDelegate, - mSelectedUserInteractor); + mSelectedUserInteractor, + mKeyguardTransitionInteractor); return controller; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java index 21928cd606ed..98d8b054716c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java @@ -66,17 +66,12 @@ public class UdfpsKeyguardViewLegacyControllerTest extends public void testViewControllerQueriesSBStateOnAttached() { mController.onViewAttached(); verify(mStatusBarStateController).getState(); - verify(mStatusBarStateController).getDozeAmount(); - final float dozeAmount = .88f; when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); - when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount); captureStatusBarStateListeners(); mController.onViewAttached(); verify(mView, atLeast(1)).setPauseAuth(true); - verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); } @Test @@ -91,19 +86,6 @@ public class UdfpsKeyguardViewLegacyControllerTest extends } @Test - public void testDozeEventsSentToView() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - final float linear = .55f; - final float eased = .65f; - mStatusBarStateListener.onDozeAmountChanged(linear, eased); - - verify(mView).onDozeAmountChanged(linear, eased, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); - } - - @Test public void testShouldPauseAuthUnpausedAlpha0() { mController.onViewAttached(); captureStatusBarStateListeners(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index 97dada27f8c0..a49150f50d07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -31,7 +31,12 @@ import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState @@ -49,6 +54,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock @@ -65,6 +71,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : private val testScope = TestScope(testDispatcher) private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository + private lateinit var transitionRepository: FakeKeyguardTransitionRepository @Mock private lateinit var bouncerLogger: TableLogBuffer @@ -78,6 +85,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : testScope.backgroundScope, bouncerLogger, ) + transitionRepository = FakeKeyguardTransitionRepository() super.setUp() } @@ -107,6 +115,12 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mock(SystemClock::class.java), mKeyguardUpdateMonitor, ) + mKeyguardTransitionInteractor = + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = transitionRepository, + ) + .keyguardTransitionInteractor return createUdfpsKeyguardViewController(/* useModernBouncer */ true) } @@ -258,4 +272,145 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : job.cancel() } + + @Test + fun aodToLockscreen_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForLockscreenAodTransitions(this) + + // WHEN transitioning from lockscreen to aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(.3f), + eq(.3f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(1f), + eq(1f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) + ) + + job.cancel() + } + + @Test + fun lockscreenToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForLockscreenAodTransitions(this) + + // WHEN transitioning from lockscreen to aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(.3f), + eq(.3f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(1f), + eq(1f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) + ) + + job.cancel() + } + + @Test + fun goneToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForGoneToAodTransition(this) + + // WHEN transitioning from lockscreen to aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(.3f), + eq(.3f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF) + ) + + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED + ) + ) + runCurrent() + // THEN doze amount is updated + verify(mView) + .onDozeAmountChanged( + eq(1f), + eq(1f), + eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF) + ) + + job.cancel() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt new file mode 100644 index 000000000000..b391b5a45799 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.data.repository + +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SimBouncerRepositoryTest : SysuiTestCase() { + @Mock lateinit var euiccManager: EuiccManager + @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private val fakeSubscriptionManagerProxy = FakeSubscriptionManagerProxy() + private val keyguardUpdateMonitorCallbacks = mutableListOf<KeyguardUpdateMonitorCallback>() + + private lateinit var underTest: SimBouncerRepositoryImpl + + @Before + fun setup() { + MockitoAnnotations.initMocks(/* testClass = */ this) + whenever(keyguardUpdateMonitor.registerCallback(any())).thenAnswer { + val cb = it.arguments[0] as KeyguardUpdateMonitorCallback + keyguardUpdateMonitorCallbacks.add(cb) + } + whenever(keyguardUpdateMonitor.removeCallback(any())).thenAnswer { + keyguardUpdateMonitorCallbacks.remove(it.arguments[0]) + } + underTest = + SimBouncerRepositoryImpl( + applicationScope = testScope.backgroundScope, + backgroundDispatcher = dispatcher, + resources = context.resources, + keyguardUpdateMonitor = keyguardUpdateMonitor, + subscriptionManager = fakeSubscriptionManagerProxy, + broadcastDispatcher = fakeBroadcastDispatcher, + euiccManager = euiccManager, + ) + } + + @Test + fun subscriptionId() = + testScope.runTest { + val subscriptionId = + emitSubscriptionIdAndCollectLastValue(underTest.subscriptionId, subId = 2) + assertThat(subscriptionId).isEqualTo(2) + } + + @Test + fun activeSubscriptionInfo() = + testScope.runTest { + fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2) + val activeSubscriptionInfo = + emitSubscriptionIdAndCollectLastValue(underTest.activeSubscriptionInfo, subId = 2) + + assertThat(activeSubscriptionInfo?.subscriptionId).isEqualTo(2) + } + + @Test + fun isLockedEsim_initialValue_isNull() = + testScope.runTest { + val isLockedEsim by collectLastValue(underTest.isLockedEsim) + assertThat(isLockedEsim).isNull() + } + + @Test + fun isLockedEsim() = + testScope.runTest { + whenever(euiccManager.isEnabled).thenReturn(true) + fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = true) + val isLockedEsim = + emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2) + assertThat(isLockedEsim).isTrue() + } + + @Test + fun isLockedEsim_notEmbedded() = + testScope.runTest { + fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = false) + val isLockedEsim = + emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2) + assertThat(isLockedEsim).isFalse() + } + + @Test + fun isSimPukLocked() = + testScope.runTest { + val isSimPukLocked = + emitSubscriptionIdAndCollectLastValue( + underTest.isSimPukLocked, + subId = 2, + isSimPuk = true + ) + assertThat(isSimPukLocked).isTrue() + } + + @Test + fun setSimPukUserInput() { + val pukCode = "00000000" + val pinCode = "1234" + underTest.setSimPukUserInput(pukCode, pinCode) + assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode) + assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode) + } + + @Test + fun setSimPukUserInput_nullPuk() { + val pukCode = null + val pinCode = "1234" + underTest.setSimPukUserInput(pukCode, pinCode) + assertThat(underTest.simPukInputModel.enteredSimPuk).isNull() + assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode) + } + + @Test + fun setSimPukUserInput_nullPin() { + val pukCode = "00000000" + val pinCode = null + underTest.setSimPukUserInput(pukCode, pinCode) + assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode) + assertThat(underTest.simPukInputModel.enteredSimPin).isNull() + } + + @Test + fun setSimPukUserInput_nullCodes() { + underTest.setSimPukUserInput() + assertThat(underTest.simPukInputModel.enteredSimPuk).isNull() + assertThat(underTest.simPukInputModel.enteredSimPin).isNull() + } + + @Test + fun setSimPinVerificationErrorMessage() = + testScope.runTest { + val errorMsg = "error" + underTest.setSimVerificationErrorMessage(errorMsg) + val msg by collectLastValue(underTest.errorDialogMessage) + assertThat(msg).isEqualTo(errorMsg) + } + + /** Emits a new sim card state and collects the last value of the flow argument. */ + @OptIn(ExperimentalCoroutinesApi::class) + private fun <T> TestScope.emitSubscriptionIdAndCollectLastValue( + flow: Flow<T>, + subId: Int = 1, + isSimPuk: Boolean = false + ): T? { + val value by collectLastValue(flow) + runCurrent() + val simState = + if (isSimPuk) { + TelephonyManager.SIM_STATE_PUK_REQUIRED + } else { + TelephonyManager.SIM_STATE_PIN_REQUIRED + } + whenever(keyguardUpdateMonitor.getNextSubIdForState(anyInt())).thenReturn(-1) + whenever(keyguardUpdateMonitor.getNextSubIdForState(simState)).thenReturn(subId) + keyguardUpdateMonitorCallbacks.forEach { + it.onSimStateChanged(subId, /* slotId= */ 0, simState) + } + runCurrent() + return value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 6ead0e9dc1f7..50d2fd22d0fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -88,6 +88,19 @@ class BouncerInteractorTest : SysuiTestCase() { } @Test + fun pinAuthMethod_sim_skipsAuthentication() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) + runCurrent() + + // We rely on TelephonyManager to authenticate the sim card. + // Additionally, authenticating the sim card does not unlock the device. + // Thus, when auth method is sim, we expect to skip here. + assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) + .isEqualTo(AuthenticationResult.SKIPPED) + } + + @Test fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) @@ -159,6 +172,19 @@ class BouncerInteractorTest : SysuiTestCase() { underTest.resetMessage() assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD) + // Too short input. + assertThat( + underTest.authenticate( + buildList { + repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + add("$time") + } + } + ) + ) + .isEqualTo(AuthenticationResult.SKIPPED) + assertThat(message).isEqualTo(MESSAGE_WRONG_PASSWORD) + // Correct input. assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) @@ -291,6 +317,14 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(imeHiddenEvent).isNotNull() } + @Test + fun intentionalUserInputEvent_registersTouchEvent() = + testScope.runTest { + assertThat(utils.powerRepository.userTouchRegistered).isFalse() + underTest.onIntentionalUserInput() + assertThat(utils.powerRepository.userTouchRegistered).isTrue() + } + private fun assertTryAgainMessage( message: String?, time: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt new file mode 100644 index 000000000000..8c53c0e3f267 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.domain.interactor + +import android.content.res.Resources +import android.telephony.PinResult +import android.telephony.SubscriptionInfo +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.res.R +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class SimBouncerInteractorTest : SysuiTestCase() { + @Mock lateinit var telephonyManager: TelephonyManager + @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock lateinit var euiccManager: EuiccManager + + private val utils = SceneTestUtils(this) + private val bouncerSimRepository = FakeSimBouncerRepository() + private val resources: Resources = context.resources + private val testScope = utils.testScope + + private lateinit var underTest: SimBouncerInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + SimBouncerInteractor( + context, + testScope.backgroundScope, + utils.testDispatcher, + bouncerSimRepository, + telephonyManager, + resources, + keyguardUpdateMonitor, + euiccManager, + utils.mobileConnectionsRepository, + ) + } + + @Test + fun getDefaultMessage() { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setDisplayName("sim").build() + ) + whenever(telephonyManager.activeModemCount).thenReturn(1) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions)) + } + + @Test + fun getDefaultMessage_isPuk() { + bouncerSimRepository.setSimPukLocked(true) + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setDisplayName("sim").build() + ) + whenever(telephonyManager.activeModemCount).thenReturn(1) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint)) + } + + @Test + fun getDefaultMessage_isEsimLocked() { + bouncerSimRepository.setLockedEsim(true) + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setDisplayName("sim").build() + ) + whenever(telephonyManager.activeModemCount).thenReturn(1) + + val msg = resources.getString(R.string.kg_sim_pin_instructions) + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_sim_lock_esim_instructions, msg)) + } + + @Test + fun getDefaultMessage_multipleSims() { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setDisplayName("sim").build() + ) + whenever(telephonyManager.activeModemCount).thenReturn(2) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions_multi, "sim")) + } + + @Test + fun getDefaultMessage_multipleSims_isPuk() { + bouncerSimRepository.setSimPukLocked(true) + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setDisplayName("sim").build() + ) + whenever(telephonyManager.activeModemCount).thenReturn(2) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint_multi, "sim")) + } + + @Test + fun getDefaultMessage_multipleSims_emptyDisplayName() { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build()) + whenever(telephonyManager.activeModemCount).thenReturn(2) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions)) + } + + @Test + fun getDefaultMessage_multipleSims_emptyDisplayName_isPuk() { + bouncerSimRepository.setSimPukLocked(true) + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build()) + whenever(telephonyManager.activeModemCount).thenReturn(2) + + assertThat(underTest.getDefaultMessage()) + .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint)) + } + + @Test + fun resetSimPukUserInput() { + bouncerSimRepository.setSimPukUserInput("00000000", "1234") + + assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isEqualTo("00000000") + assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isEqualTo("1234") + + underTest.resetSimPukUserInput() + + assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isNull() + assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isNull() + } + + @Test + fun disableEsim() = + testScope.runTest { + val portIndex = 1 + bouncerSimRepository.setActiveSubscriptionInfo( + SubscriptionInfo.Builder().setPortIndex(portIndex).build() + ) + + underTest.disableEsim() + runCurrent() + + verify(euiccManager) + .switchToSubscription( + eq(INVALID_SUBSCRIPTION_ID), + eq(portIndex), + ArgumentMatchers.any() + ) + } + + @Test + fun verifySimPin() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(false) + whenever(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager) + whenever(telephonyManager.supplyIccLockPin(anyString())) + .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1)) + + val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0)) + runCurrent() + assertThat(msg).isNull() + + verify(keyguardUpdateMonitor).reportSimUnlocked(1) + } + + @Test + fun verifySimPin_incorrect_oneRemainingAttempt() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(false) + whenever(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager) + whenever(telephonyManager.supplyIccLockPin(anyString())) + .thenReturn( + PinResult( + PinResult.PIN_RESULT_TYPE_INCORRECT, + 1, + ) + ) + + val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0)) + runCurrent() + + assertThat(msg).isNull() + val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage) + assertThat(errorDialogMessage) + .isEqualTo( + "Enter SIM PIN. You have 1 remaining attempt before you must contact" + + " your carrier to unlock your device." + ) + } + + @Test + fun verifySimPin_incorrect_threeRemainingAttempts() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(false) + whenever(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager) + whenever(telephonyManager.supplyIccLockPin(anyString())) + .thenReturn( + PinResult( + PinResult.PIN_RESULT_TYPE_INCORRECT, + 3, + ) + ) + + val msg = underTest.verifySim(listOf(0, 0, 0, 0)) + runCurrent() + + assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.") + } + + @Test + fun verifySimPin_notCorrectLength_tooShort() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(false) + + val msg = underTest.verifySim(listOf(0)) + + assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) + } + + @Test + fun verifySimPin_notCorrectLength_tooLong() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(false) + + val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + + assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) + } + + @Test + fun verifySimPuk() = + testScope.runTest { + whenever(telephonyManager.createForSubscriptionId(anyInt())) + .thenReturn(telephonyManager) + whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString())) + .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1)) + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(true) + + var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint)) + + msg = underTest.verifySim(listOf(0, 0, 0, 0)) + assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint)) + + msg = underTest.verifySim(listOf(0, 0, 0, 0)) + assertThat(msg).isNull() + + runCurrent() + verify(keyguardUpdateMonitor).reportSimUnlocked(1) + } + + @Test + fun verifySimPuk_inputTooShort() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(true) + val msg = underTest.verifySim(listOf(0, 0, 0, 0)) + assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint)) + } + + @Test + fun verifySimPuk_pinNotCorrectLength() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(true) + + underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + + val msg = underTest.verifySim(listOf(0, 0, 0)) + assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint)) + } + + @Test + fun verifySimPuk_confirmedPinDoesNotMatch() = + testScope.runTest { + bouncerSimRepository.setSubscriptionId(1) + bouncerSimRepository.setSimPukLocked(true) + + underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0)) + underTest.verifySim(listOf(0, 0, 0, 0)) + + val msg = underTest.verifySim(listOf(0, 0, 0, 1)) + assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint)) + } + + @Test + fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() { + bouncerSimRepository.setSimVerificationErrorMessage("abc") + assertThat(bouncerSimRepository.errorDialogMessage.value).isNotNull() + + underTest.onErrorDialogDismissed() + + assertThat(bouncerSimRepository.errorDialogMessage.value).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index cfcb54574144..63c992bd7854 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -48,6 +48,8 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true), + simBouncerInteractor = utils.simBouncerInteractor, + authenticationMethod = AuthenticationMethodModel.Pin, ) @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index f4346b56676d..75d6a007b4aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -233,6 +233,7 @@ class BouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pin, AuthenticationMethodModel.Password, AuthenticationMethodModel.Pattern, + AuthenticationMethodModel.Sim, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index c498edf0e971..9b1e9585979a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -78,12 +78,28 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { lockDeviceAndOpenPasswordBouncer() assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) - assertThat(password).isEqualTo("") + assertThat(password).isEmpty() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Password) } @Test + fun onHidden_resetsPasswordInputAndMessage() = + testScope.runTest { + val message by collectLastValue(bouncerViewModel.message) + val password by collectLastValue(underTest.password) + lockDeviceAndOpenPasswordBouncer() + + underTest.onPasswordInputChanged("password") + assertThat(message?.text).isNotEqualTo(ENTER_YOUR_PASSWORD) + assertThat(password).isNotEmpty() + + underTest.onHidden() + assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) + assertThat(password).isEmpty() + } + + @Test fun onPasswordInputChanged() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) @@ -121,7 +137,7 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { underTest.onPasswordInputChanged("wrong") underTest.onAuthenticateKeyPressed() - assertThat(password).isEqualTo("") + assertThat(password).isEmpty() assertThat(message?.text).isEqualTo(WRONG_PASSWORD) } @@ -134,14 +150,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Password ) utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - underTest.onShown() - // Enter nothing. + switchToScene(SceneKey.Bouncer) + + // No input entered. underTest.onAuthenticateKeyPressed() - assertThat(password).isEqualTo("") + assertThat(password).isEmpty() assertThat(message?.text).isEqualTo(ENTER_YOUR_PASSWORD) } @@ -182,32 +197,33 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { assertThat(password).isEqualTo("password") // The user doesn't confirm the password, but navigates back to the lockscreen instead. - sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + switchToScene(SceneKey.Lockscreen) // The user navigates to the bouncer again. - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - - underTest.onShown() + switchToScene(SceneKey.Bouncer) // Ensure the previously-entered password is not shown. assertThat(password).isEmpty() assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } + private fun TestScope.switchToScene(toScene: SceneKey) { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer + val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer + sceneInteractor.changeScene(SceneModel(toScene), "reason") + sceneInteractor.onSceneChanged(SceneModel(toScene), "reason") + if (bouncerShown) underTest.onShown() + if (bouncerHidden) underTest.onHidden() + runCurrent() + + assertThat(currentScene).isEqualTo(SceneModel(toScene)) + } + private fun TestScope.lockDeviceAndOpenPasswordBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password) utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - - assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) - .isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.onShown() - runCurrent() + switchToScene(SceneKey.Bouncer) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 3f5ddba23165..125fe680db21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -373,15 +373,23 @@ class PatternBouncerViewModelTest : SysuiTestCase() { ) } + private fun TestScope.switchToScene(toScene: SceneKey) { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer + val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer + sceneInteractor.changeScene(SceneModel(toScene), "reason") + sceneInteractor.onSceneChanged(SceneModel(toScene), "reason") + if (bouncerShown) underTest.onShown() + if (bouncerHidden) underTest.onHidden() + runCurrent() + + assertThat(currentScene).isEqualTo(SceneModel(toScene)) + } + private fun TestScope.lockDeviceAndOpenPatternBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern) utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) - .isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.onShown() - runCurrent() + switchToScene(SceneKey.Bouncer) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 6da69519000c..c30e405ab911 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -63,6 +63,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), + simBouncerInteractor = utils.simBouncerInteractor, + authenticationMethod = AuthenticationMethodModel.Pin, ) @Before @@ -74,49 +76,77 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) - utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - - underTest.onShown() + lockDeviceAndOpenPinBouncer() assertThat(message?.text).ignoringCase().isEqualTo(ENTER_YOUR_PIN) assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) assertThat(underTest.authenticationMethod).isEqualTo(AuthenticationMethodModel.Pin) } @Test + fun simBouncerViewModel_simAreaIsVisible() = + testScope.runTest { + val underTest = + PinBouncerViewModel( + applicationContext = context, + viewModelScope = testScope.backgroundScope, + interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), + simBouncerInteractor = utils.simBouncerInteractor, + authenticationMethod = AuthenticationMethodModel.Sim, + ) + + assertThat(underTest.isSimAreaVisible).isTrue() + } + + @Test + fun onErrorDialogDismissed_clearsDialogMessage() = + testScope.runTest { + val dialogMessage by collectLastValue(underTest.errorDialogMessage) + utils.simBouncerRepository.setSimVerificationErrorMessage("abc") + assertThat(dialogMessage).isEqualTo("abc") + + underTest.onErrorDialogDismissed() + + assertThat(dialogMessage).isNull() + } + + @Test + fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() = + testScope.runTest { + val underTest = + PinBouncerViewModel( + applicationContext = context, + viewModelScope = testScope.backgroundScope, + interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), + simBouncerInteractor = utils.simBouncerInteractor, + authenticationMethod = AuthenticationMethodModel.Sim, + ) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + val hintedPinLength by collectLastValue(underTest.hintedPinLength) + + assertThat(hintedPinLength).isNull() + } + + @Test fun onPinButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.onShown() - runCurrent() + lockDeviceAndOpenPinBouncer() underTest.onPinButtonClicked(1) assertThat(message?.text).isEmpty() assertThat(pin).containsExactly(1) - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test fun onBackspaceButtonClicked() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) lockDeviceAndOpenPinBouncer() @@ -128,7 +158,6 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(message?.text).isEmpty() assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test @@ -176,9 +205,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() - FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> - underTest.onPinButtonClicked(digit) - } + FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) underTest.onAuthenticateButtonClicked() @@ -226,9 +253,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(authResult).isFalse() // Enter the correct PIN: - FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> - underTest.onPinButtonClicked(digit) - } + FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) assertThat(message?.text).isEmpty() underTest.onAuthenticateButtonClicked() @@ -244,9 +269,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { collectLastValue(authenticationInteractor.authenticationChallengeResult) lockDeviceAndOpenPinBouncer() - FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> - underTest.onPinButtonClicked(digit) - } + FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) assertThat(authResult).isTrue() } @@ -275,31 +298,21 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onShown_againAfterSceneChange_resetsPin() = testScope.runTest { - val currentScene by collectLastValue(sceneInteractor.desiredScene) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) lockDeviceAndOpenPinBouncer() // The user types a PIN. - FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> - underTest.onPinButtonClicked(digit) - } + FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked) assertThat(pin).isNotEmpty() // The user doesn't confirm the PIN, but navigates back to the lockscreen instead. - sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) + switchToScene(SceneKey.Lockscreen) // The user navigates to the bouncer again. - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) - - underTest.onShown() + switchToScene(SceneKey.Bouncer) // Ensure the previously-entered PIN is not shown. assertThat(pin).isEmpty() - assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } @Test @@ -354,16 +367,35 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } + @Test + fun isDigitButtonAnimationEnabled() = + testScope.runTest { + val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + assertThat(isAnimationEnabled).isFalse() + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + assertThat(isAnimationEnabled).isTrue() + } + + private fun TestScope.switchToScene(toScene: SceneKey) { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer + val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer + sceneInteractor.changeScene(SceneModel(toScene), "reason") + sceneInteractor.onSceneChanged(SceneModel(toScene), "reason") + if (bouncerShown) underTest.onShown() + if (bouncerHidden) underTest.onHidden() + runCurrent() + + assertThat(currentScene).isEqualTo(SceneModel(toScene)) + } + private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - - assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) - .isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.onShown() - runCurrent() + switchToScene(SceneKey.Bouncer) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index a6c4f1929333..af4bf367c466 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -240,7 +240,7 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun contentOrdering() = + fun ordering_smartspaceBeforeUmoBeforeWidgets() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index abd9f2846d2f..0004f52bc1c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -90,6 +90,16 @@ class DeviceEntryInteractorTest : SysuiTestCase() { } @Test + fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() = + testScope.runTest { + utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) + utils.deviceEntryRepository.setUnlocked(true) + + val isUnlocked by collectLastValue(underTest.isUnlocked) + assertThat(isUnlocked).isFalse() + } + + @Test fun isDeviceEntered_onLockscreenWithSwipe_isFalse() = testScope.runTest { val isDeviceEntered by collectLastValue(underTest.isDeviceEntered) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt new file mode 100644 index 000000000000..e8eda8096b1e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractorTest.kt @@ -0,0 +1,119 @@ +/* + * 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.deviceentry.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryUdfpsInteractorTest : SysuiTestCase() { + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + private lateinit var biometricsRepository: FakeBiometricSettingsRepository + + private lateinit var underTest: DeviceEntryUdfpsInteractor + + @Before + fun setUp() { + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() + biometricsRepository = FakeBiometricSettingsRepository() + + underTest = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = fingerprintAuthRepository, + biometricSettingsRepository = biometricsRepository, + ) + } + + @Test + fun udfpsSupported_rearFp_false() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsRearFps() + assertThat(isUdfpsSupported).isFalse() + } + + @Test + fun udfpsSupoprted() = runTest { + val isUdfpsSupported by collectLastValue(underTest.isUdfpsSupported) + fingerprintPropertyRepository.supportsUdfps() + assertThat(isUdfpsSupported).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isTrue() + } + + @Test + fun udfpsEnrolledAndEnabled_rearFp_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsRearFps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun udfpsEnrolledAndEnabled_notEnrolledOrEnabled_false() = runTest { + val isUdfpsEnrolledAndEnabled by collectLastValue(underTest.isUdfpsEnrolledAndEnabled) + fingerprintPropertyRepository.supportsUdfps() + biometricsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + assertThat(isUdfpsEnrolledAndEnabled).isFalse() + } + + @Test + fun isListeningForUdfps() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isTrue() + } + + @Test + fun isListeningForUdfps_rearFp_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsRearFps() + fingerprintAuthRepository.setIsRunning(true) + assertThat(isListeningForUdfps).isFalse() + } + + @Test + fun isListeningForUdfps_notRunning_false() = runTest { + val isListeningForUdfps by collectLastValue(underTest.isListeningForUdfps) + fingerprintPropertyRepository.supportsUdfps() + fingerprintAuthRepository.setIsRunning(false) + assertThat(isListeningForUdfps).isFalse() + } +} 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 5eab2fc73fe6..df52265384fa 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 @@ -84,8 +84,8 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInOffset_updatesOnResolutionScaleChange() = testScope.runTest { - val udfpsBurnInOffsetX by collectLastValue(underTest.udfpsBurnInXOffset) - val udfpsBurnInOffsetY by collectLastValue(underTest.udfpsBurnInYOffset) + val udfpsBurnInOffsetX by collectLastValue(underTest.deviceEntryIconXOffset) + val udfpsBurnInOffsetY by collectLastValue(underTest.deviceEntryIconYOffset) assertThat(udfpsBurnInOffsetX).isEqualTo(burnInOffset) assertThat(udfpsBurnInOffsetY).isEqualTo(burnInOffset) @@ -101,7 +101,7 @@ class BurnInInteractorTest : SysuiTestCase() { @Test fun udfpsBurnInProgress_updatesOnDozeTimeTick() = testScope.runTest { - val udfpsBurnInProgress by collectLastValue(underTest.udfpsBurnInProgress) + val udfpsBurnInProgress by collectLastValue(underTest.udfpsProgress) assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress) setBurnInProgress(.88f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt index 7fb0dd55eef2..bc4c2376765d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt @@ -413,6 +413,13 @@ class InWindowLauncherUnlockAnimationInteractorTest : SysuiTestCase() { ) transitionRepository.sendTransitionStep( TransitionStep( + transitionState = TransitionState.CANCELED, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( transitionState = TransitionState.STARTED, from = KeyguardState.GONE, to = KeyguardState.AOD, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 29b546bd49ad..4f7d9444020c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -26,13 +26,14 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN -import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -136,7 +137,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Test fun startedKeyguardStateTests() = testScope.runTest { - val finishedSteps by collectValues(underTest.startedKeyguardState) + val startedStates by collectValues(underTest.startedKeyguardState) runCurrent() val steps = mutableListOf<TransitionStep>() @@ -153,7 +154,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { runCurrent() } - assertThat(finishedSteps).isEqualTo(listOf(OFF, PRIMARY_BOUNCER, AOD, GONE)) + assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE)) } @Test @@ -162,12 +163,12 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { val steps = mutableListOf<TransitionStep>() - steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) - steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) - steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) steps.forEach { @@ -175,7 +176,9 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { runCurrent() } - assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5])) + // Ignore the default state. + assertThat(finishedSteps.subList(1, finishedSteps.size)) + .isEqualTo(listOf(steps[2], steps[5])) } @Test @@ -650,6 +653,81 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { )) } + @Test + fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest { + val finishedStates by collectValues(underTest.finishedKeyguardState) + + // We default FINISHED in LOCKSCREEN. + assertEquals(listOf( + LOCKSCREEN + ), finishedStates) + + sendSteps( + TransitionStep(LOCKSCREEN, AOD, 0f, STARTED), + TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING), + TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED), + ) + + // We're FINISHED in AOD. + assertEquals(listOf( + LOCKSCREEN, + AOD, + ), finishedStates) + + // Transition back to LOCKSCREEN. + sendSteps( + TransitionStep(AOD, LOCKSCREEN, 0f, STARTED), + TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING), + TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED), + ) + + // We're FINISHED in LOCKSCREEN. + assertEquals(listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), finishedStates) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0f, STARTED), + TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING), + ) + + // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in + // LOCKSCREEN. + assertEquals(listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), finishedStates) + + sendSteps( + TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED), + ) + + // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN. + assertEquals(listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + ), finishedStates) + + sendSteps( + TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED), + TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING), + TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED), + ) + + // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to + // LOCKSCREEN after the cancellation. + assertEquals(listOf( + LOCKSCREEN, + AOD, + LOCKSCREEN, + LOCKSCREEN, + ), finishedStates) + } + private suspend fun sendSteps(vararg steps: TransitionStep) { steps.forEach { repository.sendTransitionStep(it) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index c29210270caf..bf23bf875ad3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -618,11 +618,26 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Test fun dozingToLockscreenCannotBeInterruptedByDreaming() = testScope.runTest { + transitionRepository.sendTransitionSteps( + KeyguardState.LOCKSCREEN, + KeyguardState.DOZING, + testScheduler + ) // GIVEN a prior transition has started to LOCKSCREEN transitionRepository.sendTransitionStep( TransitionStep( from = KeyguardState.DOZING, to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "KeyguardTransitionScenariosTest", + ) + ) + runCurrent() + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, value = 0.5f, transitionState = TransitionState.RUNNING, ownerName = "KeyguardTransitionScenariosTest", diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index c02add1d0ecb..03a1f7a3d6ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -39,6 +39,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Spy @@ -120,6 +121,9 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { @Test fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() = testScope.runTest { + runCurrent() + reset(fakeLightRevealScrimRepository) + fakeKeyguardTransitionRepository.sendTransitionStep( TransitionStep( transitionState = TransitionState.STARTED, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 3442df62a441..2dfc13258d63 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 @@ -123,30 +123,30 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { runCurrent() // THEN burn in offsets are 0 - assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + assertThat(burnInOffsets?.progress).isEqualTo(0f) + assertThat(burnInOffsets?.y).isEqualTo(0) + assertThat(burnInOffsets?.x).isEqualTo(0) // WHEN we're in the middle of the doze amount change keyguardRepository.setDozeAmount(.50f) runCurrent() // THEN burn in is updated (between 0 and the full offset) - assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) - assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) - assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + assertThat(burnInOffsets?.progress).isGreaterThan(0f) + assertThat(burnInOffsets?.y).isGreaterThan(0) + assertThat(burnInOffsets?.x).isGreaterThan(0) + assertThat(burnInOffsets?.progress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.y).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.x).isLessThan(burnInXOffset) // WHEN we're fully dozing keyguardRepository.setDozeAmount(1f) runCurrent() // THEN burn in offsets are updated to final current values (for the given time) - assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) - assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) - assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.y).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.x).isEqualTo(burnInXOffset) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 43d70adf26b0..76c258935727 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.items.ClockSection import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection -import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection @@ -49,6 +48,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.Optional @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) @@ -60,7 +60,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection @Mock - private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection + private lateinit var defaultAmbientIndicationAreaSection: Optional<KeyguardSection> @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection @@ -98,7 +98,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { fun replaceViews() { val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(null, constraintLayout) - underTest.sections.forEach { verify(it).addViews(constraintLayout) } + underTest.sections.forEach { verify(it)?.addViews(constraintLayout) } } @Test @@ -110,7 +110,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(prevBlueprint, constraintLayout) underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach { - verify(it, never()).addViews(constraintLayout) + verify(it, never())?.addViews(constraintLayout) } verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout) @@ -121,6 +121,6 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() underTest.applyConstraints(cs) - underTest.sections.forEach { verify(it).applyConstraints(cs) } + underTest.sections.forEach { verify(it)?.applyConstraints(cs) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 71313c8a5bf3..75bdcddf516b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -24,12 +24,14 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController +import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R @@ -42,6 +44,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.mockito.Answers import org.mockito.Mock +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -77,7 +80,9 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { notificationPanelView, featureFlags, { lockIconViewController }, - { DeviceEntryIconViewModel() }, + { mock(DeviceEntryIconViewModel::class.java) }, + { mock(DeviceEntryForegroundViewModel::class.java) }, + { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..f282481ba01c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToGoneTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "AodToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..517149c99a62 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,135 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + underTest = + AodToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryBackgroundView_udfps_alphaFadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.2f) + + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(.6f) + + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundView_rearFp_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.3f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(0.6f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "AodToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt new file mode 100644 index 000000000000..96f69462accf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt @@ -0,0 +1,81 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class AodToOccludedTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: AodToOccludedTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = AodToOccludedTransitionViewModel(interactor) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { Truth.assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.OCCLUDED, + value = value, + transitionState = state, + ownerName = "AodToOccludedTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..5dccc3b1d05f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DozingToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var testScope: TestScope + private lateinit var underTest: DozingToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = + DozingToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + ) + } + + @Test + fun deviceEntryParentViewShows() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(1f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "DozingToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index 6d47aed58dac..fd125e099f1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -35,6 +41,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -44,22 +51,34 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: DreamingToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() val interactor = KeyguardTransitionInteractorFactory.create( scope = TestScope().backgroundScope, repository = repository, ) .keyguardTransitionInteractor - underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) + underTest = + DreamingToLockscreenTransitionViewModel( + interactor, + mock(), + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = FakeBiometricSettingsRepository(), + ), + ) } @Test @@ -129,6 +148,78 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(4) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewAppear() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackground_noUdfps_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, STARTED)) + repository.sendTransitionStep(step(0f)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.2f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(0) // no updates + + job.cancel() + } + + @Test fun lockscreenTranslationY() = runTest(UnconfinedTestDispatcher()) { val values = mutableListOf<Float>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt index 255f4df17244..c1444a55f7d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -19,7 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -27,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -34,11 +39,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class GoneToAodTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: GoneToAodTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var testScope: TestScope @Before @@ -47,13 +55,24 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope = TestScope(testDispatcher) repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = testScope.backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = GoneToAodTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + GoneToAodTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -63,11 +82,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { val enterFromTopTranslationY by collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopTranslationY).isEqualTo(pixels) repository.sendTransitionStep(step(.85f)) @@ -83,11 +102,11 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) - // The animation should only start > halfway through + // The animation should only start > .4f way through repository.sendTransitionStep(step(0f, TransitionState.STARTED)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) - repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.4f)) assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) repository.sendTransitionStep(step(.85f)) @@ -97,6 +116,98 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() { assertThat(enterFromTopAnimationAlpha).isEqualTo(1f) } + @Test + fun deviceEntryBackgroundViewAlpha() = + testScope.runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.01f, 1f)) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isIn(Range.closed(.99f, 1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled() = + testScope.runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = + testScope.runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..4074851490ab --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -0,0 +1,251 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR +import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToAodTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val deviceEntryRepository: FakeDeviceEntryRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + val fingerprintPropertyRepository: FakeFingerprintPropertyRepository + val biometricSettingsRepository: FakeBiometricSettingsRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToAodTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(FACE_AUTH_REFACTOR, true) + set(FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun backgroundViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun backgroundViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeNotExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(false) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 1f + values.forEach { assertThat(it).isEqualTo(1f) } + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + shadeExpanded(true) + runCurrent() + + // fade in + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.3f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading in before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.closed(.1f, .9f)) + + // finish fading out before the end of the full transition + repository.sendTransitionStep(step(.7f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFp_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + fingerprintPropertyRepository.supportsRearFps() + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.3f), + step(.7f), + step(1f), + ), + testScope = testScope, + ) + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "LockscreenToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 89a1d2b3011d..5c85357a37a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -18,88 +18,172 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher +import dagger.BindsInstance +import dagger.Component import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToDreamingTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToDreamingTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToDreamingTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } + private val testComponent: TestComponent = + DaggerLockscreenToDreamingTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) @Test fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.2f)) - repository.sendTransitionStep(step(0.3f)) - // ...up to here - repository.sendTransitionStep(step(1f)) + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.2f), + step(.3f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only three values should be present, since the dream overlay runs for a small // fraction of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) + val values by collectValues(underTest.lockscreenTranslationY(pixels)) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // And a final reset event on FINISHED - repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), // Final reset event on FINISHED + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(6) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Validate finished value assertThat(values[5]).isEqualTo(0f) + } - job.cancel() + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(.5f), + step(1f), + step(1f, TransitionState.FINISHED), + ), + testScope = testScope, + ) + + // immediately 0f + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt new file mode 100644 index 000000000000..1494c92cdb06 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt @@ -0,0 +1,84 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToGoneTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: LockscreenToGoneTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + LockscreenToGoneTransitionViewModel( + interactor, + ) + } + + @Test + fun deviceEntryParentViewHides() = runTest { + val deviceEntryParentViewAlpha by collectValues(underTest.deviceEntryParentViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) } + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + value = value, + transitionState = state, + ownerName = "LockscreenToGoneTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index 41f8856d5ca2..4cbefa3d12ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -18,109 +18,185 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.collectValues +import com.android.runCurrent +import com.android.runTest import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.junit.Before +import dagger.BindsInstance +import dagger.Component import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { - private lateinit var underTest: LockscreenToOccludedTransitionViewModel - private lateinit var repository: FakeKeyguardTransitionRepository - - @Before - fun setUp() { - repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = LockscreenToOccludedTransitionViewModel(interactor) + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToOccludedTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } } + private val testComponent: TestComponent = + DaggerLockscreenToOccludedTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + @Test fun lockscreenFadeOut() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - - val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.1f)) - repository.sendTransitionStep(step(0.4f)) - repository.sendTransitionStep(step(0.7f)) - // ...up to here - repository.sendTransitionStep(step(1f)) - + testComponent.runTest { + val values by collectValues(underTest.lockscreenAlpha) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.1f), + step(.4f), + step(.7f), // ...up to here + step(1f), + ), + testScope = testScope, + ) // Only 3 values should be present, since the dream overlay runs for a small fraction // of the overall animation time assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } - - job.cancel() } @Test fun lockscreenTranslationY() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.5f)) - repository.sendTransitionStep(step(1f)) - // ...up to here - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), // Should start running here... + step(0f), + step(.3f), + step(.5f), + step(1f), // ...up to here + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(5) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } - - job.cancel() } @Test fun lockscreenTranslationYIsCanceled() = - runTest(UnconfinedTestDispatcher()) { - val values = mutableListOf<Float>() - + testComponent.runTest { val pixels = 100 - val job = - underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) - repository.sendTransitionStep(step(0f)) - repository.sendTransitionStep(step(0.3f)) - repository.sendTransitionStep(step(0.3f, TransitionState.CANCELED)) - + val values by collectValues(underTest.lockscreenTranslationY(pixels)) + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(0f), + step(.3f), + step(0.3f, TransitionState.CANCELED), + ), + testScope = testScope, + ) assertThat(values.size).isEqualTo(4) values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) } // Cancel will reset the translation assertThat(values[3]).isEqualTo(0) + } + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val values by collectValues(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionSteps( + steps = + listOf( + step(0f, TransitionState.STARTED), + step(.5f), + step(1f, TransitionState.FINISHED) + ), + testScope = testScope, + ) + + values.forEach { assertThat(it).isEqualTo(0f) } + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.2f)) + assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + assertThat(actual).isEqualTo(0f) - job.cancel() + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(actual).isEqualTo(0f) } private fun step( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt new file mode 100644 index 000000000000..4f564350741d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -0,0 +1,159 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.collect.Range +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<LockscreenToPrimaryBouncerTransitionViewModel> { + val repository: FakeKeyguardTransitionRepository + val keyguardRepository: FakeKeyguardRepository + val shadeRepository: FakeShadeRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + + fun shadeExpanded(expanded: Boolean) { + if (expanded) { + shadeRepository.setQsExpansion(1f) + } else { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLockscreenShadeExpansion(0f) + } + } + } + + private val testComponent: TestComponent = + DaggerLockscreenToPrimaryBouncerTransitionViewModelTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = TestMocksModule(), + ) + + @Test + fun deviceEntryParentViewAlpha_shadeExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(true) + runCurrent() + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(.2f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_shadeNotExpanded() = + testComponent.runTest { + val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) + shadeExpanded(false) + runCurrent() + + // fade out + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(1f) + + repository.sendTransitionStep(step(.1f)) + runCurrent() + Truth.assertThat(actual).isIn(Range.open(.1f, .9f)) + + // alpha is 1f before the full transition starts ending + repository.sendTransitionStep(step(0.8f)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + runCurrent() + Truth.assertThat(actual).isEqualTo(0f) + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING, + ): TransitionStep { + return TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + value = value, + transitionState = state, + ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..0eb8ff6ba966 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt @@ -0,0 +1,170 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class OccludedToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: OccludedToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + OccludedToAodTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // no updates + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "OccludedToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index ec95cb8c43f1..d0772270ed5e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -19,6 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState @@ -26,6 +30,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -35,22 +40,35 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { private lateinit var underTest: OccludedToLockscreenTransitionViewModel private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = - KeyguardTransitionInteractorFactory.create( - scope = TestScope().backgroundScope, - repository = repository, - ) - .keyguardTransitionInteractor - underTest = OccludedToLockscreenTransitionViewModel(interactor) + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + underTest = + OccludedToLockscreenTransitionViewModel( + interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + deviceEntryUdfpsInteractor = + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) } @Test @@ -113,6 +131,78 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun deviceEntryParentViewFadeIn() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<Float>() + + val job = underTest.deviceEntryParentViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + // Should start running here... + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + // ...up to here + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values.size).isEqualTo(5) + values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundViewShows() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + values.forEach { assertThat(it).isEqualTo(1f) } + + job.cancel() + } + + @Test + fun deviceEntryBackgroundView_noUdfpsEnrolled_noUpdates() = + runTest(UnconfinedTestDispatcher()) { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val values = mutableListOf<Float>() + + val job = + underTest.deviceEntryBackgroundViewAlpha.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0.1f)) + repository.sendTransitionStep(step(0.3f)) + repository.sendTransitionStep(step(0.4f)) + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(0.6f)) + repository.sendTransitionStep(step(0.8f)) + repository.sendTransitionStep(step(1f)) + + assertThat(values).isEmpty() // no updates + + job.cancel() + } + private fun step( value: Float, state: TransitionState = TransitionState.RUNNING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..350b31008478 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt @@ -0,0 +1,164 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToAodTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToAodTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor + underTest = + PrimaryBouncerToAodTransitionViewModel( + interactor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryBackgroundViewAlpha() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val deviceEntryBackgroundViewAlpha by + collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 0f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(0f) + } + + @Test + fun deviceEntryParentViewAlpha_udfpsEnrolled_fadeIn() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + + repository.sendTransitionStep(step(0.5f)) + repository.sendTransitionStep(step(.75f)) + repository.sendTransitionStep(step(1f)) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryParentViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // animation doesn't start until the end + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.95f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + @Test + fun deviceEntryParentViewAlpha_udfpsNotEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsUdfps() + biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(deviceEntryParentViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.AOD, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToAodTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt new file mode 100644 index 000000000000..24e4920c66d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt @@ -0,0 +1,142 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class PrimaryBouncerToLockscreenTransitionViewModelTest : SysuiTestCase() { + private lateinit var underTest: PrimaryBouncerToLockscreenTransitionViewModel + private lateinit var repository: FakeKeyguardTransitionRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + fingerprintPropertyRepository = FakeFingerprintPropertyRepository() + biometricSettingsRepository = FakeBiometricSettingsRepository() + + underTest = + PrimaryBouncerToLockscreenTransitionViewModel( + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor, + DeviceEntryUdfpsInteractor( + fingerprintPropertyRepository = fingerprintPropertyRepository, + fingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository(), + biometricSettingsRepository = biometricSettingsRepository, + ), + ) + } + + @Test + fun deviceEntryParentViewAlpha() = runTest { + val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.4f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.85f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f)) + assertThat(deviceEntryParentViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_udfpsEnrolled_show() = runTest { + fingerprintPropertyRepository.supportsUdfps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + + // immediately 1f + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(0.1f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.3f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(.5f)) + assertThat(bgViewAlpha).isEqualTo(1f) + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isEqualTo(1f) + } + + @Test + fun deviceEntryBackgroundViewAlpha_rearFpEnrolled_noUpdates() = runTest { + fingerprintPropertyRepository.supportsRearFps() + val bgViewAlpha by collectLastValue(underTest.deviceEntryBackgroundViewAlpha) + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(0.5f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(.75f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f)) + assertThat(bgViewAlpha).isNull() + + repository.sendTransitionStep(step(1f, TransitionState.FINISHED)) + assertThat(bgViewAlpha).isNull() + } + + private fun step( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return TransitionStep( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ownerName = "PrimaryBouncerToLockscreenTransitionViewModelTest" + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index a2eb5ef9e463..db7c9876f21b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -35,7 +35,7 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -50,6 +50,7 @@ import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -74,7 +75,7 @@ import org.mockito.junit.MockitoJUnit @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var lockHost: MediaHost @@ -91,6 +92,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController + @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock lateinit var logger: MediaViewLogger @Captor private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)> @@ -101,12 +103,12 @@ class MediaHierarchyManagerTest : SysuiTestCase() { ArgumentCaptor<(DreamOverlayStateController.Callback)> @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var mediaHierarchyManager: MediaHierarchyManager + private lateinit var isQsBypassingShade: MutableStateFlow<Boolean> private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true) private val communalInteractor = CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor - private val notifPanelEvents = ShadeExpansionStateManager() private val settings = FakeSettings() private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler @@ -122,6 +124,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) fakeHandler = FakeHandler(testableLooper.looper) whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) + isQsBypassingShade = MutableStateFlow(false) + whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade) mediaHierarchyManager = MediaHierarchyManager( context, @@ -135,7 +139,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { communalInteractor, configurationController, wakefulnessLifecycle, - notifPanelEvents, + shadeInteractor, settings, fakeHandler, testScope.backgroundScope, @@ -430,17 +434,21 @@ class MediaHierarchyManagerTest : SysuiTestCase() { assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue() } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() { - notifPanelEvents.notifyExpandImmediateChange(true) - goToLockscreen() - enterGuidedTransformation() - whenever(lockHost.visible).thenReturn(true) - whenever(qsHost.visible).thenReturn(true) - whenever(qqsHost.visible).thenReturn(true) + fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() = + testScope.runTest { + runCurrent() + isQsBypassingShade.value = true + runCurrent() + goToLockscreen() + enterGuidedTransformation() + whenever(lockHost.visible).thenReturn(true) + whenever(qsHost.visible).thenReturn(true) + whenever(qqsHost.visible).thenReturn(true) - assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() - } + assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse() + } @Test fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsFalse_with_active() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt index 83145bd8bee8..3be50b1baedf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt @@ -35,15 +35,13 @@ import android.widget.TextView import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.IUndoMediaTransferCallback -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.media.taptotransfer.MediaTttFlags import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController @@ -113,7 +111,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { private lateinit var uiEventLogger: MediaTttSenderUiEventLogger private lateinit var tempViewUiEventLogger: TemporaryViewUiEventLogger private val defaultTimeout = context.resources.getInteger(R.integer.heads_up_notification_decay) - private val featureFlags = FakeFeatureFlags() @Before fun setUp() { @@ -163,9 +160,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() { fakeWakeLockBuilder, fakeClock, tempViewUiEventLogger, - featureFlags ) - featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) chipbarCoordinator.start() underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 5e4c954a0b26..1f7a02962ce2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -16,6 +16,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessSliderController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager @@ -61,6 +62,8 @@ class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var configuration: Configuration @Mock private lateinit var pagedTileLayout: PagedTileLayout + private val sceneContainerFlags = FakeSceneContainerFlags() + private lateinit var controller: QSPanelController private val testableResources: TestableResources = mContext.orCreateTestableResources @@ -96,7 +99,8 @@ class QSPanelControllerTest : SysuiTestCase() { brightnessSliderFactory, falsingManager, statusBarKeyguardViewManager, - ResourcesSplitShadeStateController() + ResourcesSplitShadeStateController(), + sceneContainerFlags, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 555484cc17f8..8acde3637576 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -20,6 +20,7 @@ import android.content.Context import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import org.junit.After import org.junit.Before import org.junit.Test @@ -42,6 +43,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context + private val sceneContainerFlags = FakeSceneContainerFlags() + private lateinit var controller: QuickStatusBarHeaderController @Before @@ -51,7 +54,11 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(view.isAttachedToWindow).thenReturn(true) `when`(view.context).thenReturn(context) - controller = QuickStatusBarHeaderController(view, quickQSPanelController) + controller = QuickStatusBarHeaderController( + view, + quickQSPanelController, + sceneContainerFlags, + ) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt index 3b6bfeeb4ca2..3808c7ee926b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt @@ -32,6 +32,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -67,17 +72,24 @@ class BluetoothTileDialogTest : SysuiTestCase() { private val fakeSystemClock = FakeSystemClock() + private lateinit var scheduler: TestCoroutineScheduler + private lateinit var dispatcher: CoroutineDispatcher + private lateinit var testScope: TestScope private lateinit var icon: Pair<Drawable, String> private lateinit var bluetoothTileDialog: BluetoothTileDialog private lateinit var deviceItem: DeviceItem @Before fun setUp() { + scheduler = TestCoroutineScheduler() + dispatcher = UnconfinedTestDispatcher(scheduler) + testScope = TestScope(dispatcher) bluetoothTileDialog = BluetoothTileDialog( ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -111,29 +123,33 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testShowDialog_displayBluetoothDevice() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = false ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = false - ) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(adapter.itemCount).isEqualTo(1) - assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) - assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) - assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + assertThat(adapter.itemCount).isEqualTo(1) + assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME) + assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY) + assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon) + } } @Test @@ -147,6 +163,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -173,6 +190,7 @@ class BluetoothTileDialogTest : SysuiTestCase() { ENABLED, subtitleResId, bluetoothTileDialogCallback, + dispatcher, fakeSystemClock, uiEventLogger, logger, @@ -190,33 +208,37 @@ class BluetoothTileDialogTest : SysuiTestCase() { @Test fun testOnDeviceUpdated_hideSeeAll_showPairNew() { - bluetoothTileDialog = - BluetoothTileDialog( - ENABLED, - subtitleResId, - bluetoothTileDialogCallback, - fakeSystemClock, - uiEventLogger, - logger, - mContext + testScope.runTest { + bluetoothTileDialog = + BluetoothTileDialog( + ENABLED, + subtitleResId, + bluetoothTileDialogCallback, + dispatcher, + fakeSystemClock, + uiEventLogger, + logger, + mContext + ) + bluetoothTileDialog.show() + fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE) + bluetoothTileDialog.onDeviceItemUpdated( + listOf(deviceItem), + showSeeAll = false, + showPairNewDevice = true ) - bluetoothTileDialog.show() - bluetoothTileDialog.onDeviceItemUpdated( - listOf(deviceItem), - showSeeAll = false, - showPairNewDevice = true - ) - - val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) - val pairNewLayout = - bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) - val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) - val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter - assertThat(seeAllLayout).isNotNull() - assertThat(seeAllLayout.visibility).isEqualTo(GONE) - assertThat(pairNewLayout).isNotNull() - assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) - assertThat(adapter.itemCount).isEqualTo(1) + val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group) + val pairNewLayout = + bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group) + val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list) + val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter + + assertThat(seeAllLayout).isNotNull() + assertThat(seeAllLayout.visibility).isEqualTo(GONE) + assertThat(pairNewLayout).isNotNull() + assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE) + assertThat(adapter.itemCount).isEqualTo(1) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt index 682b2d0d3983..5eca8caa7d15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt @@ -19,7 +19,9 @@ package com.android.systemui.qs.tiles.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -29,8 +31,8 @@ import org.junit.runner.RunWith class QSTileConfigProviderTest : SysuiTestCase() { private val underTest = - QSTileConfigProviderImpl( - mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }) + createQSTileConfigProviderImpl( + mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }), ) @Test @@ -43,13 +45,31 @@ class QSTileConfigProviderTest : SysuiTestCase() { underTest.getConfig(INVALID_SPEC.spec) } + @Test + fun hasConfigReturnsTrueForValidSpec() { + assertThat(underTest.hasConfig(VALID_SPEC.spec)).isTrue() + } + + @Test + fun hasConfigReturnsFalseForInvalidSpec() { + assertThat(underTest.hasConfig(INVALID_SPEC.spec)).isFalse() + } + @Test(expected = IllegalArgumentException::class) fun validatesSpecUponCreation() { - QSTileConfigProviderImpl( + createQSTileConfigProviderImpl( mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC }) ) } + private fun createQSTileConfigProviderImpl( + configs: Map<String, QSTileConfig> + ): QSTileConfigProviderImpl = + QSTileConfigProviderImpl( + configs, + mock<QsEventLogger>(), + ) + private companion object { val VALID_SPEC = TileSpec.create("valid_tile_spec") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt deleted file mode 100644 index d3b7daad792e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.tiles.viewmodel - -import android.os.UserHandle -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.MediumTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics -import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor -import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor -import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper -import com.android.systemui.qs.tiles.base.logging.QSTileLogger -import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl -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.flow.launchIn -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations - -// TODO(b/299909368): Add more tests -@MediumTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { - - @Mock private lateinit var qsTileLogger: QSTileLogger - @Mock private lateinit var qsTileAnalytics: QSTileAnalytics - - private val fakeUserRepository = FakeUserRepository() - private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() - private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() - private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() - private val fakeFalsingManager = FalsingManagerFake() - - private val testCoroutineDispatcher = StandardTestDispatcher() - private val testScope = TestScope(testCoroutineDispatcher) - - private lateinit var underTest: QSTileViewModel - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - underTest = createViewModel(testScope) - } - - @Test - fun testDoesntListenStateUntilCreated() = - testScope.runTest { - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty() - - underTest.state.launchIn(backgroundScope) - runCurrent() - - assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty() - assertThat(fakeQSTileDataInteractor.dataRequests.first()) - .isEqualTo(FakeQSTileDataInteractor.DataRequest(UserHandle.of(0))) - } - - private fun createViewModel( - scope: TestScope, - config: QSTileConfig = TEST_QS_TILE_CONFIG, - ): QSTileViewModel = - QSTileViewModelImpl( - config, - { fakeQSTileUserActionInteractor }, - { fakeQSTileDataInteractor }, - { - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState = - QSTileState.build( - { Icon.Resource(0, ContentDescription.Resource(0)) }, - "" - ) {} - } - }, - fakeDisabledByPolicyInteractor, - fakeUserRepository, - fakeFalsingManager, - qsTileAnalytics, - qsTileLogger, - FakeSystemClock(), - testCoroutineDispatcher, - scope.backgroundScope, - ) - - private companion object { - - val TEST_QS_TILE_CONFIG = QSTileConfigTestBuilder.build {} - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt new file mode 100644 index 000000000000..3a0ebdbd6a17 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.MediumTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectValues +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@MediumTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun stateReceivedForTheData() = + testScope.runTest { + val testTileData = "test_tile_data" + val states = collectValues(underTest.state) + runCurrent() + + tileDataInteractor.emitData(testTileData) + runCurrent() + + assertThat(states()).isNotEmpty() + assertThat(states().first().label).isEqualTo(testTileData) + verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec)) + } + + @Test + fun doesntListenDataIfStateIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + + underTest.state.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListenAvailabilityIfAvailabilityIsntListened() = + testScope.runTest { + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(1) + } + + @Test + fun doesntListedDataAfterDestroy() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.destroy() + runCurrent() + + assertThat(tileDataInteractor.dataSubscriptionCount.value).isEqualTo(0) + assertThat(tileDataInteractor.availabilitySubscriptionCount.value).isEqualTo(0) + } + + @Test + fun forceUpdateTriggersData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.forceUpdate() + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.ForceUpdate::class.java) + verify(qsTileLogger).logForceUpdate(eq(tileConfig.tileSpec)) + } + + @Test + fun userChangeUpdatesData() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.dataRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.DataRequest(USER)) + } + + @Test + fun userChangeUpdatesAvailability() = + testScope.runTest { + underTest.isAvailable.launchIn(backgroundScope) + runCurrent() + + underTest.onUserChanged(USER) + runCurrent() + + assertThat(tileDataInteractor.availabilityRequests.last()) + .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER)) + } + + private fun createViewModel( + scope: TestScope, + config: QSTileConfig = tileConfig, + ): QSTileViewModel = + QSTileViewModelImpl( + config, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + private companion object { + + val USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt new file mode 100644 index 000000000000..ea8acc714f3a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.viewmodel + +import androidx.test.filters.MediumTest +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor +import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** Tests all possible [QSTileUserAction]s. If you need */ +@MediumTest +@RunWith(Parameterized::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSTileViewModelUserInputTest : SysuiTestCase() { + + @Mock private lateinit var qsTileLogger: QSTileLogger + @Mock private lateinit var qsTileAnalytics: QSTileAnalytics + + @Parameter lateinit var userAction: QSTileUserAction + + private val tileConfig = + QSTileConfigTestBuilder.build { policy = QSTilePolicy.Restricted("test_restriction") } + + private val userRepository = FakeUserRepository() + private val tileDataInteractor = FakeQSTileDataInteractor<String>() + private val tileUserActionInteractor = FakeQSTileUserActionInteractor<String>() + private val disabledByPolicyInteractor = FakeDisabledByPolicyInteractor() + private val falsingManager = FalsingManagerFake() + + private val testCoroutineDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testCoroutineDispatcher) + + private lateinit var underTest: QSTileViewModel + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = createViewModel(testScope) + } + + @Test + fun userInputTriggersData() = + testScope.runTest { + tileDataInteractor.emitData("initial_data") + underTest.state.launchIn(backgroundScope) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserAction(eq(userAction), eq(tileConfig.tileSpec), eq(true), eq(true)) + verify(qsTileLogger) + .logUserActionPipeline( + eq(tileConfig.tileSpec), + eq(userAction), + any(), + eq("initial_data") + ) + verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction)) + } + + @Test + fun disabledByPolicyUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + disabledByPolicyInteractor.policyResult = + DisabledByPolicyInteractor.PolicyResult.TileDisabled( + RestrictedLockUtils.EnforcedAdmin() + ) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByPolicy(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun falsedUserInputIsSkipped() = + testScope.runTest { + underTest.state.launchIn(backgroundScope) + falsingManager.setFalseLongTap(true) + falsingManager.setFalseTap(true) + runCurrent() + + underTest.onActionPerformed(userAction) + runCurrent() + + assertThat(tileDataInteractor.triggers.last()) + .isNotInstanceOf(DataUpdateTrigger.UserInput::class.java) + verify(qsTileLogger) + .logUserActionRejectedByFalsing(eq(userAction), eq(tileConfig.tileSpec)) + verify(qsTileAnalytics, never()).trackUserAction(any(), any()) + } + + @Test + fun userInputIsThrottled() = + testScope.runTest { + val inputCount = 100 + underTest.state.launchIn(backgroundScope) + + repeat(inputCount) { underTest.onActionPerformed(userAction) } + runCurrent() + + assertThat(tileDataInteractor.triggers.size).isLessThan(inputCount) + } + + private fun createViewModel(scope: TestScope): QSTileViewModel = + QSTileViewModelImpl( + tileConfig, + { tileUserActionInteractor }, + { tileDataInteractor }, + { + object : QSTileDataToStateMapper<String> { + override fun map(config: QSTileConfig, data: String): QSTileState = + QSTileState.build( + { Icon.Resource(0, ContentDescription.Resource(0)) }, + data + ) {} + } + }, + disabledByPolicyInteractor, + userRepository, + falsingManager, + qsTileAnalytics, + qsTileLogger, + FakeSystemClock(), + testCoroutineDispatcher, + scope.backgroundScope, + ) + + companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Iterable<QSTileUserAction> = + listOf( + QSTileUserAction.Click(null), + QSTileUserAction.LongClick(null), + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt new file mode 100644 index 000000000000..c1c012613fa1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.adapter + +import android.os.Bundle +import android.view.View +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.QSImpl +import com.android.systemui.qs.dagger.QSComponent +import com.android.systemui.qs.dagger.QSSceneComponent +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import javax.inject.Provider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class QSSceneAdapterImplTest : SysuiTestCase() { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val qsImplProvider = + object : Provider<QSImpl> { + val impls = mutableListOf<QSImpl>() + + override fun get(): QSImpl { + return mock<QSImpl> { + lateinit var _view: View + whenever(onComponentCreated(any(), any())).then { + _view = it.getArgument<QSComponent>(0).getRootView() + Unit + } + whenever(view).thenAnswer { _view } + } + .also { impls.add(it) } + } + } + + private val qsSceneComponentFactory = + object : QSSceneComponent.Factory { + val components = mutableListOf<QSSceneComponent>() + + override fun create(rootView: View): QSSceneComponent { + return mock<QSSceneComponent> { whenever(this.getRootView()).thenReturn(rootView) } + .also { components.add(it) } + } + } + + private val mockAsyncLayoutInflater = + mock<AsyncLayoutInflater>() { + whenever(inflate(anyInt(), nullable(), any())).then { invocation -> + val mockView = mock<View>() + invocation + .getArgument<AsyncLayoutInflater.OnInflateFinishedListener>(2) + .onInflateFinished( + mockView, + invocation.getArgument(0), + invocation.getArgument(1), + ) + } + } + + private val underTest = + QSSceneAdapterImpl( + qsSceneComponentFactory, + qsImplProvider, + testDispatcher, + testScope.backgroundScope, + { mockAsyncLayoutInflater }, + ) + + @Test + fun inflate() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + assertThat(qsImpl).isNull() + + underTest.inflate(context) + runCurrent() + + assertThat(qsImpl).isNotNull() + assertThat(qsImpl).isSameInstanceAs(qsImplProvider.impls[0]) + val inOrder = inOrder(qsImpl!!) + inOrder.verify(qsImpl!!).onCreate(nullable()) + inOrder + .verify(qsImpl!!) + .onComponentCreated( + eq(qsSceneComponentFactory.components[0]), + any(), + ) + } + + @Test + fun initialState_closed() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + + with(qsImpl!!) { + verify(this).setQsVisible(false) + verify(this) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(false) + verify(this).setExpanded(false) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun state_qqs() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + clearInvocations(qsImpl!!) + + underTest.setState(QSSceneAdapter.State.QQS) + with(qsImpl!!) { + verify(this).setQsVisible(true) + verify(this) + .setQsExpansion( + /* expansion= */ 0f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(true) + verify(this).setExpanded(true) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun state_qs() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + clearInvocations(qsImpl!!) + + underTest.setState(QSSceneAdapter.State.QS) + with(qsImpl!!) { + verify(this).setQsVisible(true) + verify(this) + .setQsExpansion( + /* expansion= */ 1f, + /* panelExpansionFraction= */ 1f, + /* proposedTranslation= */ 0f, + /* squishinessFraction= */ 1f, + ) + verify(this).setListening(true) + verify(this).setExpanded(true) + verify(this) + .setTransitionToFullShadeProgress( + /* isTransitioningToFullShade= */ false, + /* qsTransitionFraction= */ 1f, + /* qsSquishinessFraction = */ 1f, + ) + } + } + + @Test + fun customizing_QS() = + testScope.runTest { + val customizing by collectLastValue(underTest.isCustomizing) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + + assertThat(customizing).isFalse() + + underTest.setCustomizerShowing(true) + assertThat(customizing).isTrue() + + underTest.setCustomizerShowing(false) + assertThat(customizing).isFalse() + } + + @Test + fun customizing_moveToQQS_stopCustomizing() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + underTest.setCustomizerShowing(true) + + underTest.setState(QSSceneAdapter.State.QQS) + runCurrent() + verify(qsImpl!!).closeCustomizerImmediately() + } + + @Test + fun customizing_moveToClosed_stopCustomizing() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + underTest.setCustomizerShowing(true) + runCurrent() + + underTest.setState(QSSceneAdapter.State.CLOSED) + verify(qsImpl!!).closeCustomizerImmediately() + } + + @Test + fun reinflation_previousStateDestroyed() = + testScope.runTest { + val qsImpl by collectLastValue(underTest.qsImpl) + + underTest.inflate(context) + runCurrent() + val oldQsImpl = qsImpl!! + + underTest.inflate(context) + runCurrent() + val newQSImpl = qsImpl!! + + assertThat(oldQsImpl).isNotSameInstanceAs(newQSImpl) + val inOrder = inOrder(oldQsImpl, newQSImpl) + val bundleArgCaptor = argumentCaptor<Bundle>() + + inOrder.verify(oldQsImpl).onSaveInstanceState(capture(bundleArgCaptor)) + inOrder.verify(oldQsImpl).onDestroy() + assertThat(newQSImpl).isSameInstanceAs(qsImplProvider.impls[1]) + inOrder.verify(newQSImpl).onCreate(nullable()) + inOrder + .verify(newQSImpl) + .onComponentCreated( + qsSceneComponentFactory.components[1], + bundleArgCaptor.value, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index d582b9e8da5a..ef129c85ed37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -23,9 +23,12 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -53,6 +56,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { private val sceneInteractor = utils.sceneInteractor() private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } + private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() } private var mobileIconsViewModel: MobileIconsViewModel = MobileIconsViewModel( @@ -96,6 +100,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ), shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) } @@ -124,4 +129,33 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer)) } + + @Test + fun destinationsNotCustomizing() = + testScope.runTest { + val destinations by collectLastValue(underTest.destinationScenes) + qsFlexiglassAdapter.setCustomizing(false) + + assertThat(destinations) + .isEqualTo( + mapOf( + UserAction.Back to SceneModel(SceneKey.Shade), + UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade), + ) + ) + } + + @Test + fun destinationsCustomizing() = + testScope.runTest { + val destinations by collectLastValue(underTest.destinationScenes) + qsFlexiglassAdapter.setCustomizing(true) + + assertThat(destinations) + .isEqualTo( + mapOf( + UserAction.Back to SceneModel(SceneKey.QuickSettings), + ) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index d1db9c19cd2b..6a054cd9aff7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -20,6 +20,7 @@ package com.android.systemui.scene import android.telecom.TelecomManager import android.telephony.TelephonyManager +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R @@ -39,6 +40,7 @@ import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey @@ -182,6 +184,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private var bouncerSceneJob: Job? = null + private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { _, _ -> mock<View>() }) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -232,6 +236,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) utils.deviceEntryRepository.setUnlocked(false) @@ -251,6 +256,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { falsingCollector = utils.falsingCollector(), powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, + simBouncerInteractor = utils.simBouncerInteractor, + authenticationInteractor = utils.authenticationInteractor() ) startable.start() @@ -478,6 +485,32 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { verify(telecomManager).showInCallScreen(any()) } + @Test + fun showBouncer_whenLockedSimIntroduced() = + testScope.runTest { + setAuthMethod(AuthenticationMethodModel.None) + introduceLockedSim() + assertCurrentScene(SceneKey.Bouncer) + } + + @Test + fun goesToGone_whenSimUnlocked_whileDeviceUnlocked() = + testScope.runTest { + introduceLockedSim() + emulateUiSceneTransition(expectedVisible = true) + enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None) + assertCurrentScene(SceneKey.Gone) + } + + @Test + fun showLockscreen_whenSimUnlocked_whileDeviceLocked() = + testScope.runTest { + introduceLockedSim() + emulateUiSceneTransition(expectedVisible = true) + enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin) + assertCurrentScene(SceneKey.Lockscreen) + } + /** * Asserts that the current scene in the view-model matches what's expected. * @@ -678,6 +711,35 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { runCurrent() } + /** + * Enters the correct PIN in the sim bouncer UI. + * + * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN + * before proceeding. + * + * Does not assert that the device is locked or unlocked. + */ + private fun TestScope.enterSimPin( + authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None + ) { + assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") + .that(getCurrentSceneInUi()) + .isEqualTo(SceneKey.Bouncer) + val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel) + assertWithMessage("Cannot enter PIN when not using a PIN authentication method!") + .that(authMethodViewModel) + .isInstanceOf(PinBouncerViewModel::class.java) + + val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel + FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit -> + pinBouncerViewModel.onPinButtonClicked(digit) + } + pinBouncerViewModel.onAuthenticateButtonClicked() + setAuthMethod(authMethodAfterSimUnlock) + utils.mobileConnectionsRepository.isAnySimSecure.value = false + runCurrent() + } + /** Changes device wakefulness state from asleep to awake, going through intermediary states. */ private fun TestScope.wakeUpDevice() { val wakefulnessModel = powerInteractor.detailedWakefulness.value @@ -718,4 +780,10 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { runCurrent() } } + + private fun TestScope.introduceLockedSim() { + setAuthMethod(AuthenticationMethodModel.Sim) + utils.mobileConnectionsRepository.isAnySimSecure.value = true + runCurrent() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 3f032a45df41..7f4bbbe36768 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -341,6 +341,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun userInput() = testScope.runTest { + assertThat(utils.powerRepository.userTouchRegistered).isFalse() underTest.onUserInput() assertThat(utils.powerRepository.userTouchRegistered).isTrue() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 2f654e22aec6..c4ec56c906c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -89,6 +89,8 @@ class SceneContainerStartableTest : SysuiTestCase() { falsingCollector = falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, + simBouncerInteractor = utils.simBouncerInteractor, + authenticationInteractor = authenticationInteractor, ) @Before @@ -587,6 +589,64 @@ class SceneContainerStartableTest : SysuiTestCase() { verify(falsingCollector, times(2)).onBouncerHidden() } + @Test + fun switchesToBouncer_whenSimBecomesLocked() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + + utils.mobileConnectionsRepository.isAnySimSecure.value = true + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) + } + + @Test + fun switchesToLockscreen_whenSimBecomesUnlocked() = + testScope.runTest { + utils.mobileConnectionsRepository.isAnySimSecure.value = true + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + + prepareState( + initialSceneKey = SceneKey.Bouncer, + authenticationMethod = AuthenticationMethodModel.Pin, + isDeviceUnlocked = false, + ) + underTest.start() + runCurrent() + utils.mobileConnectionsRepository.isAnySimSecure.value = false + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) + } + + @Test + fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() = + testScope.runTest { + utils.mobileConnectionsRepository.isAnySimSecure.value = true + val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) + + prepareState( + initialSceneKey = SceneKey.Lockscreen, + authenticationMethod = AuthenticationMethodModel.None, + isDeviceUnlocked = true, + isLockscreenEnabled = false, + ) + underTest.start() + runCurrent() + utils.mobileConnectionsRepository.isAnySimSecure.value = false + runCurrent() + + assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) + } + private fun TestScope.prepareState( isDeviceUnlocked: Boolean = false, isBypassEnabled: Boolean = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt index 4c8b56227efa..5969bd82c361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.ReleasedFlag import com.android.systemui.flags.ResourceBooleanFlag import com.android.systemui.flags.UnreleasedFlag +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -68,6 +69,7 @@ internal class SceneContainerFlagsTest( listOf( AconfigFlags.FLAG_SCENE_CONTAINER, AconfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, + KeyguardShadeMigrationNssl.FLAG_NAME, ) .forEach { flagToken -> setFlagsRule.enableFlags(flagToken) diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt index 152be6529464..6e487cdd65b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessDialogTest.kt @@ -25,9 +25,9 @@ import android.view.ViewGroup import android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.SingleActivityFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor @@ -182,5 +182,15 @@ class BrightnessDialogTest : SysuiTestCase() { brightnessControllerFactory, mainExecutor, accessibilityMgr - ) + ) { + private var finishing = false + + override fun isFinishing(): Boolean { + return finishing + } + + override fun requestFinish() { + finishing = true + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index f4c05e0272eb..ba8a66637e8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -100,6 +100,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteracto import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; @@ -122,11 +124,12 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; import com.android.systemui.scene.SceneTestUtils; -import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -333,6 +336,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private CastController mCastController; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; + @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; protected final int mMaxUdfpsBurnInOffsetY = 5; protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); @@ -371,12 +375,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false); mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false); - mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false); mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); - mFeatureFlags.set(Flags.MIGRATE_NSSL, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); - mFeatureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false); mFeatureFlags.set(Flags.MIGRATE_CLOCKS_TO_BLUEPRINT, false); + + mSetFlagsRule.disableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); + mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); + mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = KeyguardInteractorFactory.create(); @@ -387,26 +392,27 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( StateFlowKt.MutableStateFlow(false)); - mShadeInteractor = new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( mTestScope.getBackgroundScope(), new FakeDeviceProvisioningRepository(), new FakeDisableFlagsRepository(), mDozeParameters, - new FakeSceneContainerFlags(), - mUtils::sceneInteractor, mFakeKeyguardRepository, mKeyguardTransitionInteractor, mPowerInteractor, new FakeUserSetupRepository(), mock(UserSwitcherInteractor.class), - new SharedNotificationContainerInteractor( - new FakeConfigurationRepository(), - mContext, - new ResourcesSplitShadeStateController() - ), - mShadeRepository + new ShadeInteractorLegacyImpl( + mTestScope.getBackgroundScope(), + mFakeKeyguardRepository, + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ) ); - SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor); @@ -423,7 +429,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDozeParameters, mScreenOffAnimationController, mKeyguardLogger, - mFeatureFlags, mInteractionJankMonitor, mKeyguardInteractor, mKeyguardTransitionInteractor, @@ -497,6 +502,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true); when(mQs.getView()).thenReturn(mView); when(mQSFragment.getView()).thenReturn(mView); + when(mNaturalScrollingSettingObserver.isNaturalScrollingEnabled()).thenReturn(true); doAnswer(invocation -> { mFragmentListener = invocation.getArgument(1); return null; @@ -707,7 +713,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardFaceAuthInteractor, new ResourcesSplitShadeStateController(), mPowerInteractor, - mKeyguardClockPositionAlgorithm); + mKeyguardClockPositionAlgorithm, + mNaturalScrollingSettingObserver); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, @@ -770,7 +777,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, - mFeatureFlags, mInteractionJankMonitor, mShadeLog, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index d5737643e0d4..722fb2c75081 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -59,6 +59,7 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.FaceAuthApiRequestReason; import com.android.systemui.DejankUtils; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; @@ -1105,7 +1106,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test public void nsslFlagEnabled_allowOnlyExternalTouches() { - mFeatureFlags.set(Flags.MIGRATE_NSSL, true); + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME); // This sets the dozing state that is read when onMiddleClicked is eventually invoked. mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index 7ad84d6186e7..4df7ef533c7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade -import android.os.VibrationEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.HapticFeedbackConstants @@ -26,13 +25,10 @@ import androidx.test.filters.SmallTest import com.android.internal.util.CollectionUtils import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.StatusBarState.SHADE import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED -import com.android.systemui.statusbar.VibratorHelper -import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -62,9 +58,6 @@ class NotificationPanelViewControllerWithCoroutinesTest : override fun getMainDispatcher() = Dispatchers.Main.immediate - private val ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = - VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false) - @Test fun testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() = runTest { launch(Dispatchers.Main.immediate) { givenViewAttached() } @@ -158,31 +151,8 @@ class NotificationPanelViewControllerWithCoroutinesTest : } @Test - fun doubleTapRequired_onKeyguard_oneWayHapticsDisabled_usesOldVibrate() = runTest { - launch(Dispatchers.Main.immediate) { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) - val listener = getFalsingTapListener() - mStatusBarStateController.setState(KEYGUARD) - - listener.onAdditionalTapRequired() - val packageName = mView.context.packageName - verify(mKeyguardIndicationController).showTransientIndication(anyInt()) - verify(mVibratorHelper) - .vibrate( - any(), - eq(packageName), - eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT), - eq("falsing-additional-tap-required"), - eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES) - ) - } - advanceUntilIdle() - } - - @Test - fun doubleTapRequired_onKeyguard_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest { + fun doubleTapRequired_onKeyguard_usesPerformHapticFeedback() = runTest { launch(Dispatchers.Main.immediate) { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true) val listener = getFalsingTapListener() mStatusBarStateController.setState(KEYGUARD) @@ -208,32 +178,8 @@ class NotificationPanelViewControllerWithCoroutinesTest : } @Test - fun doubleTapRequired_shadeLocked_oneWayHapticsDisabled_usesOldVibrate() = runTest { - launch(Dispatchers.Main.immediate) { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) - val listener = getFalsingTapListener() - val packageName = mView.context.packageName - mStatusBarStateController.setState(SHADE_LOCKED) - - listener.onAdditionalTapRequired() - verify(mVibratorHelper) - .vibrate( - any(), - eq(packageName), - eq(ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT), - eq("falsing-additional-tap-required"), - eq(VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES) - ) - - verify(mTapAgainViewController).show() - } - advanceUntilIdle() - } - - @Test - fun doubleTapRequired_shadeLocked_oneWayHapticsEnabled_usesPerformHapticFeedback() = runTest { + fun doubleTapRequired_shadeLocked_usesPerformHapticFeedback() = runTest { launch(Dispatchers.Main.immediate) { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true) val listener = getFalsingTapListener() mStatusBarStateController.setState(SHADE_LOCKED) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index e6cd17f448b9..8403ac59d2f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -51,7 +51,6 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; -import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; @@ -68,7 +67,6 @@ import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAni import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.FakeWindowRootViewComponent; @@ -80,6 +78,8 @@ import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -128,22 +128,22 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private SysuiColorExtractor mColorExtractor; - @Mock ColorExtractor.GradientColors mGradientColors; + @Mock private ColorExtractor.GradientColors mGradientColors; @Mock private DumpManager mDumpManager; @Mock private KeyguardSecurityModel mKeyguardSecurityModel; @Mock private KeyguardStateController mKeyguardStateController; @Mock private ScreenOffAnimationController mScreenOffAnimationController; @Mock private AuthController mAuthController; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private UserTracker mUserTracker; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; + private final Executor mMainExecutor = MoreExecutors.directExecutor(); private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); - private SceneTestUtils mUtils = new SceneTestUtils(this); - private TestScope mTestScope = mUtils.getTestScope(); + private final SceneTestUtils mUtils = new SceneTestUtils(this); + private final TestScope mTestScope = mUtils.getTestScope(); private ShadeInteractor mShadeInteractor; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -167,11 +167,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository(); FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeShadeRepository shadeRepository = new FakeShadeRepository(); - FakePowerRepository powerRepository = new FakePowerRepository(); - PowerInteractor powerInteractor = new PowerInteractor( - powerRepository, - new FalsingCollectorFake(), + PowerInteractor powerInteractor = mUtils.powerInteractor( + mUtils.getPowerRepository(), + mUtils.falsingCollector(), mScreenOffAnimationController, mStatusBarStateController); @@ -180,7 +179,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new SceneContainerRepository( mTestScope.getBackgroundScope(), mUtils.fakeSceneContainerConfig()), - powerRepository, + powerInteractor, mock(SceneLogger.class)); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); @@ -234,25 +233,26 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mKeyguardSecurityModel, mSelectedUserInteractor, powerInteractor); - - mShadeInteractor = - new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mock(DozeParameters.class), + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + new FakeUserSetupRepository(), + mock(UserSwitcherInteractor.class), + new ShadeInteractorLegacyImpl( mTestScope.getBackgroundScope(), - new FakeDeviceProvisioningRepository(), - new FakeDisableFlagsRepository(), - mock(DozeParameters.class), - sceneContainerFlags, - () -> sceneInteractor, keyguardRepository, - keyguardTransitionInteractor, - powerInteractor, - new FakeUserSetupRepository(), - mock(UserSwitcherInteractor.class), new SharedNotificationContainerInteractor( configurationRepository, mContext, new ResourcesSplitShadeStateController()), - shadeRepository); + shadeRepository + ) + ); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 5459779121c9..2dd0af78cf17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -53,7 +53,6 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.flags.Flags.MIGRATE_NSSL import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON @@ -67,6 +66,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger @@ -198,7 +198,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - featureFlagsClassic.set(MIGRATE_NSSL, false) featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false) mCommunalRepository = FakeCommunalRepository() @@ -468,7 +467,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { // AND the lock icon wants the touch whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() @@ -487,7 +486,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(false) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -506,7 +505,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) .thenReturn(true) - featureFlagsClassic.set(MIGRATE_NSSL, true) + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) // THEN touch should NOT be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index a6ab6a51bfa7..4b6290619192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -192,7 +192,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - featureFlags.set(Flags.MIGRATE_NSSL, false) featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false) testScope = TestScope() controller = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 778cfa67cad7..88a47eb81bde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -65,7 +65,10 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */ +/** + * Uses Flags.KEYGUARD_STATUS_VIEW_MIGRATE_NSSL set to false. If all goes well, this set of tests + * will be deleted. + */ @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @@ -101,11 +104,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.MIGRATE_NSSL, false) - set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) - } + featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, false) } mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 23420037e233..1f37ca09dc26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.plugins.qs.QS @@ -100,11 +101,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - featureFlags = - FakeFeatureFlags().apply { - set(Flags.MIGRATE_NSSL, true) - set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) - } + mSetFlagsRule.enableFlags(KeyguardShadeMigrationNssl.FLAG_NAME) + featureFlags = FakeFeatureFlags().apply { set(Flags.QS_CONTAINER_GRAPH_OPTIMIZER, true) } mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 6d0488706133..26b84e372d5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -37,7 +37,6 @@ import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; -import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlagsClassic; @@ -58,7 +57,6 @@ import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; @@ -70,6 +68,8 @@ import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shade.transition.ShadeTransitionController; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -169,6 +169,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected CastController mCastController; @Mock protected UserSwitcherInteractor mUserSwitcherInteractor; @Mock protected SelectedUserInteractor mSelectedUserInteractor; + protected FakeDisableFlagsRepository mDisableFlagsRepository = new FakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); @@ -198,12 +199,11 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new FakeDeviceProvisioningRepository(); deviceProvisioningRepository.setDeviceProvisioned(true); FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); - FakePowerRepository powerRepository = new FakePowerRepository(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); - PowerInteractor powerInteractor = new PowerInteractor( - powerRepository, - new FalsingCollectorFake(), + PowerInteractor powerInteractor = mUtils.powerInteractor( + mUtils.getPowerRepository(), + mUtils.falsingCollector(), mock(ScreenOffAnimationController.class), mStatusBarStateController); @@ -212,7 +212,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new SceneContainerRepository( mTestScope.getBackgroundScope(), mUtils.fakeSceneContainerConfig()), - powerRepository, + powerInteractor, mock(SceneLogger.class)); FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); @@ -251,7 +251,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new InWindowLauncherUnlockAnimationRepository(), mTestScope.getBackgroundScope(), keyguardTransitionInteractor, - () -> new FakeKeyguardSurfaceBehindRepository(), + FakeKeyguardSurfaceBehindRepository::new, mock(ActivityManagerWrapper.class) ) ); @@ -269,25 +269,26 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); - mShadeInteractor = - new ShadeInteractor( + mShadeInteractor = new ShadeInteractorImpl( + mTestScope.getBackgroundScope(), + deviceProvisioningRepository, + mDisableFlagsRepository, + mDozeParameters, + mKeyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + new FakeUserSetupRepository(), + mUserSwitcherInteractor, + new ShadeInteractorLegacyImpl( mTestScope.getBackgroundScope(), - deviceProvisioningRepository, - mDisableFlagsRepository, - mDozeParameters, - sceneContainerFlags, - () -> sceneInteractor, mKeyguardRepository, - keyguardTransitionInteractor, - powerInteractor, - new FakeUserSetupRepository(), - mUserSwitcherInteractor, new SharedNotificationContainerInteractor( configurationRepository, mContext, splitShadeStateController), mShadeRepository - ); + ) + ); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -355,7 +356,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mAccessibilityManager, mLockscreenGestureLogger, mMetricsLogger, - mFeatureFlags, mInteractionJankMonitor, mShadeLogger, mDumpManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index 7cb6d931ea8b..997e0e27ef9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -285,6 +285,20 @@ public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest } @Test + public void updateQsState_fullscreenTrue() { + mQsController.setExpanded(true); + mQsController.updateQsState(); + assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isTrue(); + } + + @Test + public void updateQsState_fullscreenFalse() { + mQsController.setExpanded(false); + mQsController.updateQsState(); + assertThat(mShadeRepository.getLegacyQsFullscreen().getValue()).isFalse(); + } + + @Test public void shadeExpanded_onKeyguard() { mStatusBarStateController.setState(KEYGUARD); // set maxQsExpansion in NPVC diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index 20b19fd16f4f..5f8777ddcbb6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -207,4 +207,22 @@ class ShadeRepositoryImplTest : SysuiTestCase() { underTest.setLegacyIsQsExpanded(true) assertThat(underTest.legacyIsQsExpanded.value).isEqualTo(true) } + + @Test + fun updateLegacyExpandImmediate() = + testScope.runTest { + assertThat(underTest.legacyExpandImmediate.value).isEqualTo(false) + + underTest.setLegacyExpandImmediate(true) + assertThat(underTest.legacyExpandImmediate.value).isEqualTo(true) + } + + @Test + fun updateLegacyQsFullscreen() = + testScope.runTest { + assertThat(underTest.legacyQsFullscreen.value).isEqualTo(false) + + underTest.setLegacyQsFullscreen(true) + assertThat(underTest.legacyQsFullscreen.value).isEqualTo(true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index ff7443f10bf3..09700e186978 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -14,7 +14,7 @@ * limitations under the License */ -package com.android.systemui.shade.data.repository +package com.android.systemui.shade.domain.interactor import android.app.StatusBarManager.DISABLE2_NONE import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE @@ -46,9 +46,7 @@ import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.ObservableTransitionState -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.phone.DozeParameters @@ -62,14 +60,12 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test @SmallTest -class ShadeInteractorTest : SysuiTestCase() { +class ShadeInteractorImplTest : SysuiTestCase() { @SysUISingleton @Component( @@ -79,7 +75,7 @@ class ShadeInteractorTest : SysuiTestCase() { UserDomainLayerModule::class, ] ) - interface TestComponent : SysUITestComponent<ShadeInteractor> { + interface TestComponent : SysUITestComponent<ShadeInteractorImpl> { val configurationRepository: FakeConfigurationRepository val deviceProvisioningRepository: FakeDeviceProvisioningRepository @@ -105,7 +101,7 @@ class ShadeInteractorTest : SysuiTestCase() { private val dozeParameters: DozeParameters = mock() private val testComponent: TestComponent = - DaggerShadeInteractorTest_TestComponent.factory() + DaggerShadeInteractorImplTest_TestComponent.factory() .create( test = this, featureFlags = @@ -446,154 +442,6 @@ class ShadeInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeExpansion_idle_onScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.Shade - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is idle on the scene - val transitionState = - MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - } - - @Test - fun lockscreenShadeExpansion_idle_onDifferentScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is idle on a different scene - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(SceneKey.Lockscreen) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_toScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN expansion matches the progress - assertThat(expansionAmount).isEqualTo(.4f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_fromScene() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 1 - assertThat(expansionAmount).isEqualTo(1f) - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN expansion reflects the progress - assertThat(expansionAmount).isEqualTo(.6f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test - fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = - testComponent.runTest() { - // GIVEN an expansion flow based on transitions to and from a scene - val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) - val expansionAmount by collectLastValue(expansion) - - // WHEN transition state is starting to between different scenes - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.Shade, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN expansion is 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition state is partially complete - progress.value = .4f - - // THEN expansion is still 0 - assertThat(expansionAmount).isEqualTo(0f) - - // WHEN transition completes - progress.value = 1f - - // THEN expansion is still 0 - assertThat(expansionAmount).isEqualTo(0f) - } - - @Test fun userInteractingWithShade_shadeDraggedUpAndDown() = testComponent.runTest() { val actual by collectLastValue(underTest.isUserInteractingWithShade) @@ -815,199 +663,6 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN user is not interacting assertThat(actual).isFalse() } - @Test - fun userInteracting_idle() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.Shade - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is idle - val transitionState = - MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_toScene_programmatic() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_toScene_userInputDriven() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = key, - progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is true - assertThat(interacting).isTrue() - } - - @Test - fun userInteracting_transitioning_fromScene_programmatic() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is false - assertThat(interacting).isFalse() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is false - assertThat(interacting).isFalse() - } - - @Test - fun userInteracting_transitioning_fromScene_userInputDriven() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val key = SceneKey.QuickSettings - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to move to the scene - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = key, - toScene = SceneKey.Lockscreen, - progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition state is partially to the scene - progress.value = .4f - - // THEN interacting is true - assertThat(interacting).isTrue() - - // WHEN transition completes - progress.value = 1f - - // THEN interacting is true - assertThat(interacting).isTrue() - } - - @Test - fun userInteracting_transitioning_toAndFromDifferentScenes() = - testComponent.runTest() { - // GIVEN an interacting flow based on transitions to and from a scene - val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade) - val interacting by collectLastValue(interactingFlow) - - // WHEN transition state is starting to between different scenes - val progress = MutableStateFlow(0f) - val transitionState = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Transition( - fromScene = SceneKey.Lockscreen, - toScene = SceneKey.QuickSettings, - progress = MutableStateFlow(0f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), - ) - ) - sceneInteractor.setTransitionState(transitionState) - - // THEN interacting is false - assertThat(interacting).isFalse() - } @Test fun isShadeTouchable_isFalse_whenFrpIsActive() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt new file mode 100644 index 000000000000..f3c875e671c4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.shade.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test + +@SmallTest +class ShadeInteractorLegacyImplTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ShadeInteractorLegacyImpl> { + + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val powerRepository: FakePowerRepository + val sceneInteractor: SceneInteractor + val shadeRepository: FakeShadeRepository + val userRepository: FakeUserRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() + + private val testComponent: TestComponent = + DaggerShadeInteractorLegacyImplTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, false) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ), + ) + + @Before + fun setUp() { + runBlocking { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + testComponent.apply { + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + } + } + } + + @Test + fun fullShadeExpansionWhenShadeLocked() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + shadeRepository.setLockscreenShadeExpansion(0.5f) + + assertThat(actual).isEqualTo(1f) + } + + @Test + fun fullShadeExpansionWhenStatusBarStateIsNotShadeLocked() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + + shadeRepository.setLockscreenShadeExpansion(0.5f) + assertThat(actual).isEqualTo(0.5f) + + shadeRepository.setLockscreenShadeExpansion(0.8f) + assertThat(actual).isEqualTo(0.8f) + } + + @Test + fun shadeExpansionWhenInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(.7f) + runCurrent() + + // THEN legacy shade expansion is passed through + assertThat(actual).isEqualTo(.7f) + } + + @Test + fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) + shadeRepository.setQsExpansion(.5f) + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(0f) + } + + @Test + fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() = + testComponent.runTest() { + val actual by collectLastValue(underTest.shadeExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyShadeExpansion(.6f) + + // THEN shade expansion is zero + assertThat(actual).isEqualTo(.6f) + } + + @Test + fun userInteractingWithShade_shadeDraggedUpAndDown() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed but tracking is not stopped + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged halfway and tracking is stopped + shadeRepository.setLegacyShadeExpansion(.6f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade completes expansion stopped + shadeRepository.setLegacyShadeExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged down halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully expanded and tracking is stopped + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadePartiallyExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade collapsed and not tracking input + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade partially expanded + shadeRepository.setLegacyShadeExpansion(.4f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN tracking is stopped + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade goes back to collapsed + shadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithShade_shadeCollapsed() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithShade) + // GIVEN shade expanded and not tracking input + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN shade tracking starts + shadeRepository.setLegacyShadeTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade dragged up halfway + shadeRepository.setLegacyShadeExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN shade fully collapsed and tracking is stopped + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } + + @Test + fun userInteractingWithQs_qsDraggedUpAndDown() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isUserInteractingWithQs) + // GIVEN qs collapsed and not tracking input + shadeRepository.setQsExpansion(0f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + + // WHEN qs tracking starts + shadeRepository.setLegacyQsTracking(true) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged down halfway + shadeRepository.setQsExpansion(.5f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully expanded but tracking is not stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs fully collapsed but tracking is not stopped + shadeRepository.setQsExpansion(0f) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs dragged halfway and tracking is stopped + shadeRepository.setQsExpansion(.6f) + shadeRepository.setLegacyQsTracking(false) + runCurrent() + + // THEN user is interacting + assertThat(actual).isTrue() + + // WHEN qs completes expansion stopped + shadeRepository.setQsExpansion(1f) + runCurrent() + + // THEN user is not interacting + assertThat(actual).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt new file mode 100644 index 000000000000..470e0c640b6d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -0,0 +1,583 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.domain.interactor + +import android.content.pm.UserInfo +import android.os.UserManager +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.TestMocksModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.ObservableTransitionState +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth +import dagger.BindsInstance +import dagger.Component +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Ignore +import org.junit.Test + +@SmallTest +class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { + + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ShadeInteractorSceneContainerImpl> { + + val configurationRepository: FakeConfigurationRepository + val keyguardRepository: FakeKeyguardRepository + val keyguardTransitionRepository: FakeKeyguardTransitionRepository + val powerRepository: FakePowerRepository + val sceneInteractor: SceneInteractor + val shadeRepository: FakeShadeRepository + val userRepository: FakeUserRepository + + @Component.Factory + interface Factory { + fun create( + @BindsInstance test: SysuiTestCase, + featureFlags: FakeFeatureFlagsClassicModule, + mocks: TestMocksModule, + ): TestComponent + } + } + + private val dozeParameters: DozeParameters = mock() + + private val testComponent: TestComponent = + DaggerShadeInteractorSceneContainerImplTest_TestComponent.factory() + .create( + test = this, + featureFlags = + FakeFeatureFlagsClassicModule { + set(Flags.FACE_AUTH_REFACTOR, false) + set(Flags.FULL_SCREEN_USER_SWITCHER, true) + }, + mocks = + TestMocksModule( + dozeParameters = dozeParameters, + ), + ) + + @Before + fun setUp() { + runBlocking { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or + UserInfo.FLAG_ADMIN or + UserInfo.FLAG_FULL, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + testComponent.apply { + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + } + } + } + + @Ignore("b/309825977") + @Test + fun qsExpansionWhenInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.qsExpansion) + + // WHEN split shade is enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, true) + configurationRepository.onAnyConfigurationChange() + val progress = MutableStateFlow(.3f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN legacy shade expansion is passed through + Truth.assertThat(actual).isEqualTo(.3f) + } + + @Ignore("b/309825977") + @Test + fun qsExpansionWhenNotInSplitShadeAndQsExpanded() = + testComponent.runTest() { + val actual by collectLastValue(underTest.qsExpansion) + + // WHEN split shade is not enabled and QS is expanded + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + overrideResource(R.bool.config_use_split_notification_shade, false) + val progress = MutableStateFlow(.3f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN shade expansion is zero + Truth.assertThat(actual).isEqualTo(.7f) + } + + @Test + fun qsFullscreen_falseWhenTransitioning() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isQsFullscreen) + + // WHEN scene transition active + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + val progress = MutableStateFlow(.3f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.QuickSettings, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN QS is not fullscreen + Truth.assertThat(actual).isFalse() + } + + @Test + fun qsFullscreen_falseWhenIdleNotQS() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isQsFullscreen) + + // WHEN Idle but not on QuickSettings scene + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Shade) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN QS is not fullscreen + Truth.assertThat(actual).isFalse() + } + + @Test + fun qsFullscreen_trueWhenIdleQS() = + testComponent.runTest() { + val actual by collectLastValue(underTest.isQsFullscreen) + + // WHEN Idle on QuickSettings scene + keyguardRepository.setStatusBarState(StatusBarState.SHADE) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.QuickSettings) + ) + sceneInteractor.setTransitionState(transitionState) + runCurrent() + + // THEN QS is fullscreen + Truth.assertThat(actual).isTrue() + } + + @Test + fun lockscreenShadeExpansion_idle_onScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.Shade + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is idle on the scene + val transitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + } + + @Test + fun lockscreenShadeExpansion_idle_onDifferentScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is idle on a different scene + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(SceneKey.Lockscreen) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_toScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN expansion matches the progress + Truth.assertThat(expansionAmount).isEqualTo(.4f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_fromScene() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val expansion = underTest.sceneBasedExpansion(sceneInteractor, key) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 1 + Truth.assertThat(expansionAmount).isEqualTo(1f) + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN expansion reflects the progress + Truth.assertThat(expansionAmount).isEqualTo(.6f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() = + testComponent.runTest() { + // GIVEN an expansion flow based on transitions to and from a scene + val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings) + val expansionAmount by collectLastValue(expansion) + + // WHEN transition state is starting to between different scenes + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.Shade, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN expansion is 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition state is partially complete + progress.value = .4f + + // THEN expansion is still 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + + // WHEN transition completes + progress.value = 1f + + // THEN expansion is still 0 + Truth.assertThat(expansionAmount).isEqualTo(0f) + } + + @Test + fun userInteracting_idle() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.Shade + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is idle + val transitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_toScene_programmatic() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_toScene_userInputDriven() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = key, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + } + + @Test + fun userInteracting_transitioning_fromScene_programmatic() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } + + @Test + fun userInteracting_transitioning_fromScene_userInputDriven() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val key = SceneKey.QuickSettings + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, key) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to move to the scene + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = key, + toScene = SceneKey.Lockscreen, + progress = progress, + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition state is partially to the scene + progress.value = .4f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + + // WHEN transition completes + progress.value = 1f + + // THEN interacting is true + Truth.assertThat(interacting).isTrue() + } + + @Test + fun userInteracting_transitioning_toAndFromDifferentScenes() = + testComponent.runTest() { + // GIVEN an interacting flow based on transitions to and from a scene + val interactingFlow = underTest.sceneBasedInteracting(sceneInteractor, SceneKey.Shade) + val interacting by collectLastValue(interactingFlow) + + // WHEN transition state is starting to between different scenes + val progress = MutableStateFlow(0f) + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Transition( + fromScene = SceneKey.Lockscreen, + toScene = SceneKey.QuickSettings, + progress = MutableStateFlow(0f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + sceneInteractor.setTransitionState(transitionState) + + // THEN interacting is false + Truth.assertThat(interacting).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index fa849fe806c2..0d3b2b372610 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -76,6 +77,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { scope = testScope.backgroundScope, ) + private val qsFlexiglassAdapter = FakeQSSceneAdapter { _, _ -> mock() } + private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel private lateinit var underTest: ShadeSceneViewModel @@ -97,6 +100,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsFlexiglassAdapter, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt index b8fe2f911b50..cb83e7c7adbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/BcSmartspaceConfigProviderTest.kt @@ -20,10 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.smartspace.config.BcSmartspaceConfigProvider -import com.android.systemui.util.mockito.whenever -import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -45,16 +42,7 @@ class BcSmartspaceConfigProviderTest : SysuiTestCase() { } @Test - fun isDefaultDateWeatherDisabled_flagIsTrue_returnsTrue() { - whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - + fun isDefaultDateWeatherDisabled_returnsTrue() { assertTrue(configProvider.isDefaultDateWeatherDisabled) } - - @Test - fun isDefaultDateWeatherDisabled_flagIsFalse_returnsFalse() { - whenever(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - - assertFalse(configProvider.isDefaultDateWeatherDisabled) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt index d925d0acd8d8..ea7c06865001 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.ExpandHelper import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.mockito.mock @@ -48,16 +49,19 @@ class DragDownHelperTest : SysuiTestCase() { private val dragDownloadCallback: LockscreenShadeTransitionController = mock() private val expandableView: ExpandableView = mock() private val expandCallback: ExpandHelper.Callback = mock() + private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver = mock() @Before fun setUp() { whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight) + whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true) dragDownHelper = DragDownHelper( falsingManager, falsingCollector, dragDownloadCallback, - mContext + naturalScrollingSettingObserver, + mContext, ).also { it.expandCallback = expandCallback } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 970a0f7a3605..43922e91eacf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -14,6 +14,7 @@ import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FakeFeatureFlagsClassicModule import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.plugins.qs.QS import com.android.systemui.power.domain.interactor.PowerInteractor @@ -99,6 +100,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var stackscroller: NotificationStackScrollLayout @Mock lateinit var statusbarStateController: SysuiStatusBarStateController @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback + @Mock lateinit var naturalScrollingSettingObserver: NaturalScrollingSettingObserver @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -123,6 +125,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { whenever(lockScreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true) whenever(lockScreenUserManager.isLockscreenPublicMode(anyInt())).thenReturn(true) whenever(keyguardBypassController.bypassEnabled).thenReturn(false) + whenever(naturalScrollingSettingObserver.isNaturalScrollingEnabled).thenReturn(true) testComponent = DaggerLockscreenShadeTransitionControllerTest_TestComponent.factory() @@ -185,6 +188,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { shadeInteractor = testComponent.shadeInteractor, powerInteractor = testComponent.powerInteractor, splitShadeStateController = ResourcesSplitShadeStateController(), + naturalScrollingSettingObserver = naturalScrollingSettingObserver, ) transitionController.addCallback(transitionControllerCallback) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 4b79a499bf90..8fa7cd291851 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository @@ -153,23 +155,25 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { mock(), mock(), powerInteractor) - shadeInteractor = ShadeInteractor( + shadeInteractor = ShadeInteractorImpl( testScope.backgroundScope, FakeDeviceProvisioningRepository(), FakeDisableFlagsRepository(), mock(), - sceneContainerFlags, - utils::sceneInteractor, keyguardRepository, keyguardTransitionInteractor, powerInteractor, FakeUserSetupRepository(), mock(), - SharedNotificationContainerInteractor( - configurationRepository, - mContext, - ResourcesSplitShadeStateController()), - shadeRepository, + ShadeInteractorLegacyImpl( + testScope.backgroundScope, + keyguardRepository, + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 9036f22a792a..8440e00a89f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -38,7 +38,6 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin @@ -205,10 +204,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - // Todo(b/261760571): flip the flag value here when feature is launched, and update relevant - // tests. - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) .thenReturn(fakePrivateLockscreenSettingUri) `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) @@ -260,17 +255,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { deviceProvisionedListener = deviceProvisionedCaptor.value } - @Test(expected = RuntimeException::class) - fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() { - // GIVEN the feature flag is disabled - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - - // WHEN we try to build the view - controller.buildAndConnectWeatherView(fakeParent) - - // THEN an exception is thrown - } - @Test fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() { // GIVEN an unprovisioned device and an attempt to connect @@ -332,6 +316,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { clearInvocations(plugin) // WHEN the session is closed + controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) + controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() @@ -376,20 +362,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { configChangeListener.onThemeChanged() // We update the new text color to match the wallpaper color - verify(smartspaceView).setPrimaryTextColor(anyInt()) - } - - @Test - fun testThemeChange_ifDecouplingEnabled_updatesTextColor() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - - // GIVEN a connected smartspace session - connectSession() - - // WHEN the theme changes - configChangeListener.onThemeChanged() - - // We update the new text color to match the wallpaper color verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setPrimaryTextColor(anyInt()) @@ -404,20 +376,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) // We pass that along to the view - verify(smartspaceView).setDozeAmount(0.7f) - } - - @Test - fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - - // GIVEN a connected smartspace session - connectSession() - - // WHEN the doze amount changes - statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f) - - // We pass that along to the view verify(dateSmartspaceView).setDozeAmount(0.7f) verify(weatherSmartspaceView).setDozeAmount(0.7f) verify(smartspaceView).setDozeAmount(0.7f) @@ -472,7 +430,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testAllTargetsAreFilteredExceptWeatherWhenNotificationsAreDisabled() { + fun testAllTargetsAreFilteredInclWeatherWhenNotificationsAreDisabled() { // GIVEN the active user doesn't allow any notifications on lockscreen setShowNotifications(userHandlePrimary, false) connectSession() @@ -488,7 +446,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { sessionListener.onTargetsAvailable(targets) // THEN all non-sensitive content is still shown - verify(plugin).onTargetsAvailable(eq(listOf(targets[3]))) + verify(plugin).onTargetsAvailable(emptyList()) } @Test @@ -519,8 +477,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + fun testSessionListener_weatherTargetIsFilteredOut() { connectSession() // WHEN we receive a list of targets @@ -670,8 +627,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + fun testSessionListener_weatherDataUpdates() { connectSession() clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) @@ -699,33 +655,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) - connectSession() - - clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) - // WHEN we receive a list of targets - val targets = listOf( - makeWeatherTargetWithExtras( - id = 1, - userHandle = userHandlePrimary, - description = "Sunny", - state = WeatherData.WeatherStateIcon.SUNNY.id, - temperature = "32", - useCelsius = false), - makeTarget(2, userHandlePrimary, isSensitive = true) - ) - - sessionListener.onTargetsAvailable(targets) - - verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> - w.description == "Sunny" && - w.state == WeatherData.WeatherStateIcon.SUNNY && - w.temperature == 32 && !w.useCelsius - }) - } - - @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -781,6 +710,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { connectSession() // WHEN we are told to cleanup + controller.stateChangeListener.onViewDetachedFromWindow(dateSmartspaceView as View) + controller.stateChangeListener.onViewDetachedFromWindow(weatherSmartspaceView as View) controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View) controller.disconnect() @@ -816,16 +747,6 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test - fun testWeatherViewUsesSameSession() { - `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) - // GIVEN a connected session - connectSession() - - // No checks is needed here, since connectSession() already checks internally that - // createSmartspaceSession is invoked only once. - } - - @Test fun testViewGetInitializedWithBypassEnabledState() { // GIVEN keyguard bypass is enabled. `when`(keyguardBypassController.bypassEnabled).thenReturn(true) @@ -853,31 +774,29 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } private fun connectSession() { - if (controller.isDateWeatherDecoupled()) { - val dateView = controller.buildAndConnectDateView(fakeParent) - dateSmartspaceView = dateView as SmartspaceView - fakeParent.addView(dateView) - controller.stateChangeListener.onViewAttachedToWindow(dateView) - - verify(dateSmartspaceView).setUiSurface( - BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) - verify(dateSmartspaceView).registerDataProvider(datePlugin) - - verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) - verify(dateSmartspaceView).setDozeAmount(0.5f) - - val weatherView = controller.buildAndConnectWeatherView(fakeParent) - weatherSmartspaceView = weatherView as SmartspaceView - fakeParent.addView(weatherView) - controller.stateChangeListener.onViewAttachedToWindow(weatherView) - - verify(weatherSmartspaceView).setUiSurface( - BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) - verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) - - verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) - verify(weatherSmartspaceView).setDozeAmount(0.5f) - } + val dateView = controller.buildAndConnectDateView(fakeParent) + dateSmartspaceView = dateView as SmartspaceView + fakeParent.addView(dateView) + controller.stateChangeListener.onViewAttachedToWindow(dateView) + + verify(dateSmartspaceView).setUiSurface( + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(dateSmartspaceView).registerDataProvider(datePlugin) + + verify(dateSmartspaceView).setPrimaryTextColor(anyInt()) + verify(dateSmartspaceView).setDozeAmount(0.5f) + + val weatherView = controller.buildAndConnectWeatherView(fakeParent) + weatherSmartspaceView = weatherView as SmartspaceView + fakeParent.addView(weatherView) + controller.stateChangeListener.onViewAttachedToWindow(weatherView) + + verify(weatherSmartspaceView).setUiSurface( + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) + verify(weatherSmartspaceView).registerDataProvider(weatherPlugin) + + verify(weatherSmartspaceView).setPrimaryTextColor(anyInt()) + verify(weatherSmartspaceView).setDozeAmount(0.5f) val view = controller.buildAndConnectView(fakeParent) smartspaceView = view as SmartspaceView @@ -918,10 +837,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { verify(smartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setDozeAmount(0.5f) - if (controller.isDateWeatherDecoupled()) { - clearInvocations(dateSmartspaceView) - clearInvocations(weatherSmartspaceView) - } + clearInvocations(dateSmartspaceView) + clearInvocations(weatherSmartspaceView) clearInvocations(smartspaceView) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index 1a04a3ea291a..87e9735394f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -389,4 +389,31 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") assertThat(isolatedIcon?.isAnimating).isFalse() } + + @Test + fun isolatedIcon_updateWhenIconDataChanges() = + testComponent.runTest { + val icon: Icon = mock() + val isolatedIcon by collectLastValue(underTest.isolatedIcon) + runCurrent() + + headsUpViewStateRepository.isolatedNotification.value = "notif1" + runCurrent() + + activeNotificationsRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { + addIndividualNotif( + activeNotificationModel( + key = "notif1", + groupKey = "group", + statusBarIcon = icon + ) + ) + } + .build() + runCurrent() + + assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1") + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 1c621613c403..3e331a6cff2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -76,6 +76,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -126,6 +127,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { DeviceProvisionedController mDeviceProvisionedController; FakeSystemClock mSystemClock; FakeGlobalSettings mGlobalSettings; + FakeEventLog mEventLog; private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider; @@ -138,6 +140,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mSystemClock = new FakeSystemClock(); mGlobalSettings = new FakeGlobalSettings(); mGlobalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON); + mEventLog = new FakeEventLog(); mNotifInterruptionStateProvider = new NotificationInterruptStateProviderImpl( @@ -155,7 +158,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { mUserTracker, mDeviceProvisionedController, mSystemClock, - mGlobalSettings); + mGlobalSettings, + mEventLog); mNotifInterruptionStateProvider.mUseHeadsUp = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 1d2055ec2e30..acc5cea0c8f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -44,7 +44,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision statusBarStateController, keyguardStateController, headsUpManager, - logger, + oldLogger, mainHandler, flags, keyguardNotificationVisibilityProvider, @@ -53,6 +53,7 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision deviceProvisionedController, systemClock, globalSettings, + eventLog ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index 722b1704bb18..9e7df5f4a9f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -33,15 +33,17 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro ambientDisplayConfiguration, batteryController, deviceProvisionedController, + eventLog, globalSettings, headsUpManager, keyguardNotificationVisibilityProvider, keyguardStateController, - logger, + newLogger, mainHandler, powerManager, statusBarStateController, systemClock, + uiEventLogger, userTracker, ) } @@ -222,14 +224,14 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro private class TestCondition( types: Set<VisualInterruptionType>, val onShouldSuppress: () -> Boolean - ) : VisualInterruptionCondition(types = types, reason = "") { + ) : VisualInterruptionCondition(types = types, reason = "test condition") { override fun shouldSuppress(): Boolean = onShouldSuppress() } private class TestFilter( types: Set<VisualInterruptionType>, val onShouldSuppress: (NotificationEntry) -> Boolean = { true } - ) : VisualInterruptionFilter(types = types, reason = "") { + ) : VisualInterruptionFilter(types = types, reason = "test filter") { override fun shouldSuppress(entry: NotificationEntry) = onShouldSuppress(entry) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 0f298365bbc5..5dcb6c9e9527 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -20,7 +20,9 @@ import android.app.ActivityManager import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.FLAG_BUBBLE +import android.app.Notification.FLAG_FOREGROUND_SERVICE import android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED +import android.app.Notification.FLAG_USER_INITIATED_JOB import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_CHILDREN import android.app.Notification.GROUP_ALERT_SUMMARY @@ -45,8 +47,12 @@ import android.os.PowerManager import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import android.provider.Settings.Global.HEADS_UP_ON +import com.android.internal.logging.UiEventLogger.UiEventEnum import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.FakeStatusBarStateController @@ -58,8 +64,13 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.util.FakeEventLog import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.settings.FakeGlobalSettings @@ -76,19 +87,36 @@ import org.junit.Test import org.mockito.Mockito.`when` as whenever abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { + private val fakeLogBuffer = + LogBuffer( + name = "FakeLog", + maxSize = 1, + logcatEchoTracker = + object : LogcatEchoTracker { + override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = + true + + override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true + }, + systrace = false + ) + private val leakCheck = LeakCheckedTest.SysuiLeakCheck() protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) protected val batteryController = FakeBatteryController(leakCheck) protected val deviceProvisionedController = FakeDeviceProvisionedController() + protected val eventLog = FakeEventLog() protected val flags: NotifPipelineFlags = mock() - protected val globalSettings = FakeGlobalSettings() + protected val globalSettings = + FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) } protected val headsUpManager: HeadsUpManager = mock() protected val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() protected val keyguardStateController = FakeKeyguardStateController(leakCheck) - protected val logger: NotificationInterruptLogger = mock() protected val mainHandler = FakeHandler(Looper.getMainLooper()) + protected val newLogger = VisualInterruptionDecisionLogger(fakeLogBuffer) + protected val oldLogger = NotificationInterruptLogger(fakeLogBuffer) protected val powerManager: PowerManager = mock() protected val statusBarStateController = FakeStatusBarStateController() protected val systemClock = FakeSystemClock() @@ -116,14 +144,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { @Before fun setUp() { - globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) - val user = UserInfo(ActivityManager.getCurrentUser(), "Current user", /* flags = */ 0) userTracker.set(listOf(user), /* currentUserIndex = */ 0) - whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any())) - .thenReturn(false) - provider.start() } @@ -131,18 +154,21 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldPeek() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPeek_settingDisabled() { ensurePeekState { hunSettingEnabled = false } assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPeek_packageSnoozed_withoutFsi() { ensurePeekState { hunSnoozed = true } assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test @@ -151,6 +177,13 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { forEachPeekableFsiState { ensurePeekState { hunSnoozed = true } assertShouldHeadsUp(entry) + + // The old code logs a UiEvent when a HUN snooze is bypassed because the notification + // has an FSI, but that doesn't fit into the new code's suppressor-based logic, so we're + // not reimplementing it. + if (provider !is NotificationInterruptStateProviderWrapper) { + assertNoEventsLogged() + } } } @@ -158,42 +191,49 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldNotPeek_alreadyBubbled() { ensurePeekState { statusBarState = SHADE } assertShouldNotHeadsUp(buildPeekEntry { isBubble = true }) + assertNoEventsLogged() } @Test fun testShouldPeek_isBubble_shadeLocked() { ensurePeekState { statusBarState = SHADE_LOCKED } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) + assertNoEventsLogged() } @Test fun testShouldPeek_isBubble_keyguard() { ensurePeekState { statusBarState = KEYGUARD } assertShouldHeadsUp(buildPeekEntry { isBubble = true }) + assertNoEventsLogged() } @Test fun testShouldNotPeek_dnd() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_PEEK }) + assertNoEventsLogged() } @Test fun testShouldNotPeek_notImportant() { ensurePeekState() assertShouldNotHeadsUp(buildPeekEntry { importance = IMPORTANCE_DEFAULT }) + assertNoEventsLogged() } @Test fun testShouldNotPeek_screenOff() { ensurePeekState { isScreenOn = false } assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPeek_dreaming() { ensurePeekState { isDreaming = true } assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test @@ -203,27 +243,94 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test - fun testShouldPeek_notQuiteOldEnoughWhen() { + fun testLogsHunOldWhen() { + assertNoEventsLogged() + + ensurePeekState() + val entry = buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) } + + // The old code logs the "old when" UiEvent unconditionally, so don't expect that it hasn't. + if (provider !is NotificationInterruptStateProviderWrapper) { + provider.makeUnloggedHeadsUpDecision(entry) + assertNoEventsLogged() + } + + provider.makeAndLogHeadsUpDecision(entry) + assertUiEventLogged(HUN_SUPPRESSED_OLD_WHEN, entry.sbn.uid, entry.sbn.packageName) + assertNoSystemEventLogged() + } + + @Test + fun testShouldPeek_oldWhen_now() { + ensurePeekState() + assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) }) + assertNoEventsLogged() + } + + @Test + fun testShouldPeek_oldWhen_notOldEnough() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) + assertNoEventsLogged() } @Test - fun testShouldPeek_zeroWhen() { + fun testShouldPeek_oldWhen_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) + assertNoEventsLogged() + } + + @Test + fun testShouldPeek_oldWhen_negativeWhen() { + ensurePeekState() + assertShouldHeadsUp(buildPeekEntry { whenMs = -1L }) + assertNoEventsLogged() } @Test - fun testShouldPeek_oldWhenButFsi() { + fun testShouldPeek_oldWhen_fullScreenIntent() { ensurePeekState() assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) + assertNoEventsLogged() + } + + @Test + fun testShouldPeek_oldWhen_foregroundService() { + ensurePeekState() + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isForegroundService = true + } + ) + assertNoEventsLogged() + } + + @Test + fun testShouldPeek_oldWhen_userInitiatedJob() { + ensurePeekState() + assertShouldHeadsUp( + buildPeekEntry { + whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) + isUserInitiatedJob = true + } + ) + assertNoEventsLogged() + } + + @Test + fun testShouldNotPeek_hiddenOnKeyguard() { + ensurePeekState({ keyguardShouldHideNotification = true }) + assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -232,6 +339,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -240,6 +348,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -248,54 +357,28 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test fun testShouldPulse() { ensurePulseState() assertShouldHeadsUp(buildPulseEntry()) - } - - @Test - fun testShouldPulse_defaultLegacySuppressor() { - ensurePulseState() - withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } - } - - @Test - fun testShouldNotPulse_legacySuppressInterruptions() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesInterruptions) { - assertShouldNotHeadsUp(buildPulseEntry()) - } - } - - @Test - fun testShouldPulse_legacySuppressAwakeInterruptions() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { - assertShouldHeadsUp(buildPulseEntry()) - } - } - - @Test - fun testShouldPulse_legacySuppressAwakeHeadsUp() { - ensurePulseState() - withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { - assertShouldHeadsUp(buildPulseEntry()) - } + assertNoEventsLogged() } @Test fun testShouldNotPulse_disabled() { ensurePulseState { pulseOnNotificationsEnabled = false } assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPulse_batterySaver() { ensurePulseState { isAodPowerSave = true } assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test @@ -304,152 +387,177 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { assertShouldNotHeadsUp( buildPulseEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_AMBIENT } ) + assertNoEventsLogged() } @Test fun testShouldNotPulse_visibilityOverridePrivate() { ensurePulseState() assertShouldNotHeadsUp(buildPulseEntry { visibilityOverride = VISIBILITY_PRIVATE }) + assertNoEventsLogged() } @Test fun testShouldNotPulse_importanceLow() { ensurePulseState() assertShouldNotHeadsUp(buildPulseEntry { importance = IMPORTANCE_LOW }) + assertNoEventsLogged() } - private fun withPeekAndPulseEntry( - extendEntry: EntryBuilder.() -> Unit, - block: (NotificationEntry) -> Unit - ) { - ensurePeekState() - block(buildPeekEntry(extendEntry)) + @Test + fun testShouldNotPulse_hiddenOnKeyguard() { + ensurePulseState({ keyguardShouldHideNotification = true }) + assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() + } + @Test + fun testShouldPulse_defaultLegacySuppressor() { ensurePulseState() - block(buildPulseEntry(extendEntry)) + withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertAll() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_ALL - }) { - assertShouldHeadsUp(it) + fun testShouldNotPulse_legacySuppressInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesInterruptions) { + assertShouldNotHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test - fun testShouldHeadsUp_groupedSummaryNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeInterruptions() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { + assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test - fun testShouldNotHeadsUp_groupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldNotHeadsUp(it) + fun testShouldPulse_legacySuppressAwakeHeadsUp() { + ensurePulseState() + withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { + assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } - @Test - fun testShouldHeadsUp_ungroupedSummaryNotif_groupAlertChildren() { - withPeekAndPulseEntry({ - isGrouped = false - isGroupSummary = true - groupAlertBehavior = GROUP_ALERT_CHILDREN - }) { - assertShouldHeadsUp(it) - } + private fun withPeekAndPulseEntry( + extendEntry: EntryBuilder.() -> Unit, + block: (NotificationEntry) -> Unit + ) { + ensurePeekState() + block(buildPeekEntry(extendEntry)) + + ensurePulseState() + block(buildPulseEntry(extendEntry)) } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertAll() { + fun testShouldNotHeadsUp_suppressiveGroupAlertBehavior() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_ALL + groupAlertBehavior = GROUP_ALERT_SUMMARY }) { - assertShouldHeadsUp(it) + assertShouldNotHeadsUp(it) + assertNoEventsLogged() } } @Test - fun testShouldHeadsUp_groupedChildNotif_groupAlertChildren() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notSuppressive() { withPeekAndPulseEntry({ isGrouped = true isGroupSummary = false groupAlertBehavior = GROUP_ALERT_CHILDREN }) { assertShouldHeadsUp(it) + assertNoEventsLogged() } } @Test - fun testShouldNotHeadsUp_groupedChildNotif_groupAlertSummary() { - withPeekAndPulseEntry({ - isGrouped = true - isGroupSummary = false - groupAlertBehavior = GROUP_ALERT_SUMMARY - }) { - assertShouldNotHeadsUp(it) - } - } - - @Test - fun testShouldHeadsUp_ungroupedChildNotif_groupAlertSummary() { + fun testShouldHeadsUp_suppressiveGroupAlertBehavior_notGrouped() { withPeekAndPulseEntry({ isGrouped = false isGroupSummary = false groupAlertBehavior = GROUP_ALERT_SUMMARY }) { assertShouldHeadsUp(it) + assertNoEventsLogged() } } @Test fun testShouldNotHeadsUp_justLaunchedFsi() { - withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { assertShouldNotHeadsUp(it) } + withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { + assertShouldNotHeadsUp(it) + assertNoEventsLogged() + } } @Test fun testShouldBubble_withIntentAndIcon() { ensureBubbleState() assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false }) + assertNoEventsLogged() } @Test fun testShouldBubble_withShortcut() { ensureBubbleState() assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true }) + assertNoEventsLogged() } @Test - fun testShouldNotBubble_notAllowed() { + fun testShouldBubble_suppressiveGroupAlertBehavior() { ensureBubbleState() - assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + assertShouldBubble( + buildBubbleEntry { + isGrouped = true + isGroupSummary = false + groupAlertBehavior = GROUP_ALERT_SUMMARY + } + ) + assertNoEventsLogged() + } + + @Test + fun testShouldNotBubble_notABubble() { + ensureBubbleState() + assertShouldNotBubble( + buildBubbleEntry { + isBubble = false + hasBubbleMetadata = false + } + ) + assertNoEventsLogged() } @Test - fun testShouldNotBubble_noBubbleMetadata() { + fun testShouldNotBubble_missingBubbleMetadata() { ensureBubbleState() assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false }) + assertNoEventsLogged() + } + + @Test + fun testShouldNotBubble_notAllowedToBubble() { + ensureBubbleState() + assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + assertNoEventsLogged() } @Test fun testShouldBubble_defaultLegacySuppressor() { ensureBubbleState() withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -458,6 +566,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldNotBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -466,6 +575,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldNotBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -474,23 +584,22 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test - fun testShouldNotAlert_hiddenOnKeyguard() { - ensurePeekState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPeekEntry()) - - ensurePulseState({ keyguardShouldHideNotification = true }) - assertShouldNotHeadsUp(buildPulseEntry()) - + fun testShouldNotBubble_hiddenOnKeyguard() { ensureBubbleState({ keyguardShouldHideNotification = true }) assertShouldNotBubble(buildBubbleEntry()) + assertNoEventsLogged() } @Test fun testShouldNotFsi_noFullScreenIntent() { - forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) } + forEachFsiState { + assertShouldNotFsi(buildFsiEntry { hasFsi = false }) + assertNoEventsLogged() + } } @Test @@ -502,6 +611,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { isStickyAndNotDemoted = true } ) + assertNoEventsLogged() } } @@ -512,12 +622,16 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT }, expectWouldInterruptWithoutDnd = true ) + assertNoEventsLogged() } } @Test fun testShouldNotFsi_notImportantEnough() { - forEachFsiState { assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT }) } + forEachFsiState { + assertShouldNotFsi(buildFsiEntry { importance = IMPORTANCE_DEFAULT }) + assertNoEventsLogged() + } } @Test @@ -530,6 +644,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { }, expectWouldInterruptWithoutDnd = false ) + assertNoEventsLogged() } } @@ -547,6 +662,27 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testLogsFsiSuppressiveGroupAlertBehavior() { + ensureNotInteractiveFsiState() + val entry = buildFsiEntry { + isGrouped = true + isGroupSummary = true + groupAlertBehavior = GROUP_ALERT_CHILDREN + } + + val decision = provider.makeUnloggedFullScreenIntentDecision(entry) + assertNoEventsLogged() + + provider.logFullScreenIntentDecision(decision) + assertUiEventLogged( + FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, + entry.sbn.uid, + entry.sbn.packageName + ) + assertSystemEventLogged("231322873", entry.sbn.uid, "groupAlertBehavior") + } + + @Test fun testShouldFsi_suppressiveGroupAlertBehavior_notGrouped() { forEachFsiState { assertShouldFsi( @@ -556,6 +692,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_CHILDREN } ) + assertNoEventsLogged() } } @@ -585,26 +722,52 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testLogsFsiSuppressiveBubbleMetadata() { + ensureNotInteractiveFsiState() + val entry = buildFsiEntry { + hasBubbleMetadata = true + bubbleSuppressesNotification = true + } + + val decision = provider.makeUnloggedFullScreenIntentDecision(entry) + assertNoEventsLogged() + + provider.logFullScreenIntentDecision(decision) + assertUiEventLogged( + FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, + entry.sbn.uid, + entry.sbn.packageName + ) + assertSystemEventLogged("274759612", entry.sbn.uid, "bubbleMetadata") + } + + @Test fun testShouldNotFsi_packageSuspended() { - forEachFsiState { assertShouldNotFsi(buildFsiEntry { packageSuspended = true }) } + forEachFsiState { + assertShouldNotFsi(buildFsiEntry { packageSuspended = true }) + assertNoEventsLogged() + } } @Test fun testShouldFsi_notInteractive() { ensureNotInteractiveFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test fun testShouldFsi_dreaming() { ensureDreamingFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test fun testShouldFsi_keyguard() { ensureKeyguardFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test @@ -612,6 +775,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { forEachPeekableFsiState { ensurePeekState() assertShouldNotFsi(buildFsiEntry()) + assertNoEventsLogged() } } @@ -620,6 +784,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { forEachPeekableFsiState { ensurePeekState { hunSnoozed = true } assertShouldNotFsi(buildFsiEntry()) + assertNoEventsLogged() } } @@ -627,18 +792,21 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldFsi_lockedShade() { ensureLockedShadeFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test fun testShouldFsi_keyguardOccluded() { ensureKeyguardOccludedFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test fun testShouldFsi_deviceNotProvisioned() { ensureDeviceNotProvisionedFsiState() assertShouldFsi(buildFsiEntry()) + assertNoEventsLogged() } @Test @@ -648,9 +816,23 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testLogsFsiNoHunOrKeyguard() { + ensureNoHunOrKeyguardFsiState() + val entry = buildFsiEntry() + + val decision = provider.makeUnloggedFullScreenIntentDecision(entry) + assertNoEventsLogged() + + provider.logFullScreenIntentDecision(decision) + assertUiEventLogged(FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, entry.sbn.uid, entry.sbn.packageName) + assertSystemEventLogged("231322873", entry.sbn.uid, "no hun or keyguard") + } + + @Test fun testShouldFsi_defaultLegacySuppressor() { forEachFsiState { withLegacySuppressor(neverSuppresses) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -658,6 +840,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldFsi_suppressInterruptions() { forEachFsiState { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -667,6 +850,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -674,6 +858,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldFsi_suppressAwakeHeadsUp() { forEachFsiState { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -855,12 +1040,12 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected fun assertShouldHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertTrue("unexpected suppressed HUN: ${it.logReason}", it.shouldInterrupt) } protected fun assertShouldNotHeadsUp(entry: NotificationEntry) = - provider.makeUnloggedHeadsUpDecision(entry).let { + provider.makeAndLogHeadsUpDecision(entry).let { assertFalse("unexpected unsuppressed HUN: ${it.logReason}", it.shouldInterrupt) } @@ -876,6 +1061,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected fun assertShouldFsi(entry: NotificationEntry) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertTrue("unexpected suppressed FSI: ${it.logReason}", it.shouldInterrupt) } @@ -884,10 +1070,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { expectWouldInterruptWithoutDnd: Boolean? = null ) = provider.makeUnloggedFullScreenIntentDecision(entry).let { + provider.logFullScreenIntentDecision(it) assertFalse("unexpected unsuppressed FSI: ${it.logReason}", it.shouldInterrupt) if (expectWouldInterruptWithoutDnd != null) { assertEquals( - "unexpected unsuppressed-without-DND FSI: ${it.logReason}", + "unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}", expectWouldInterruptWithoutDnd, it.wouldInterruptWithoutDnd ) @@ -895,22 +1082,35 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected class EntryBuilder(val context: Context) { - var importance = IMPORTANCE_DEFAULT - var suppressedVisualEffects: Int? = null - var whenMs: Long? = null - var visibilityOverride: Int? = null - var hasFsi = false - var canBubble: Boolean? = null - var isBubble = false - var hasBubbleMetadata = false + // Set on BubbleMetadata: var bubbleIsShortcut = false - var bubbleSuppressesNotification: Boolean? = null + var bubbleSuppressesNotification = false + + // Set on Notification.Builder: + var whenMs: Long? = null var isGrouped = false - var isGroupSummary: Boolean? = null + var isGroupSummary = false var groupAlertBehavior: Int? = null - var hasJustLaunchedFsi = false + var hasBubbleMetadata = false + var hasFsi = false + + // Set on Notification: + var isForegroundService = false + var isUserInitiatedJob = false + var isBubble = false var isStickyAndNotDemoted = false - var packageSuspended: Boolean? = null + + // Set on NotificationEntryBuilder: + var importance = IMPORTANCE_DEFAULT + var canBubble: Boolean? = null + + // Set on NotificationEntry: + var hasJustLaunchedFsi = false + + // Set on ModifiedRankingBuilder: + var packageSuspended = false + var visibilityOverride: Int? = null + var suppressedVisualEffects: Int? = null private fun buildBubbleMetadata(): BubbleMetadata { val builder = @@ -928,71 +1128,87 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { ) } - bubbleSuppressesNotification?.let { builder.setSuppressNotification(it) } + if (bubbleSuppressesNotification) { + builder.setSuppressNotification(true) + } return builder.build() } fun build() = Notification.Builder(context, TEST_CHANNEL_ID) - .apply { - setContentTitle(TEST_CONTENT_TITLE) - setContentText(TEST_CONTENT_TEXT) + .also { nb -> + nb.setContentTitle(TEST_CONTENT_TITLE) + nb.setContentText(TEST_CONTENT_TEXT) - if (hasFsi) { - setFullScreenIntent(mock(), /* highPriority = */ true) + whenMs?.let { nb.setWhen(it) } + + if (isGrouped) { + nb.setGroup(TEST_GROUP_KEY) } - whenMs?.let { setWhen(it) } + if (isGroupSummary) { + nb.setGroupSummary(true) + } + + groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) } if (hasBubbleMetadata) { - setBubbleMetadata(buildBubbleMetadata()) + nb.setBubbleMetadata(buildBubbleMetadata()) } - if (isGrouped) { - setGroup(TEST_GROUP_KEY) + if (hasFsi) { + nb.setFullScreenIntent(mock(), /* highPriority = */ true) } - - isGroupSummary?.let { setGroupSummary(it) } - - groupAlertBehavior?.let { setGroupAlertBehavior(it) } } .build() - .apply { + .also { n -> + if (isForegroundService) { + n.flags = n.flags or FLAG_FOREGROUND_SERVICE + } + + if (isUserInitiatedJob) { + n.flags = n.flags or FLAG_USER_INITIATED_JOB + } + if (isBubble) { - flags = flags or FLAG_BUBBLE + n.flags = n.flags or FLAG_BUBBLE } if (isStickyAndNotDemoted) { - flags = flags or FLAG_FSI_REQUESTED_BUT_DENIED + n.flags = n.flags or FLAG_FSI_REQUESTED_BUT_DENIED } } .let { NotificationEntryBuilder().setNotification(it) } - .apply { - setPkg(TEST_PACKAGE) - setOpPkg(TEST_PACKAGE) - setTag(TEST_TAG) - - setImportance(importance) - setChannel(NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance)) + .also { neb -> + neb.setPkg(TEST_PACKAGE) + neb.setOpPkg(TEST_PACKAGE) + neb.setTag(TEST_TAG) + + neb.setImportance(importance) + neb.setChannel( + NotificationChannel(TEST_CHANNEL_ID, TEST_CHANNEL_NAME, importance) + ) - canBubble?.let { setCanBubble(it) } + canBubble?.let { neb.setCanBubble(it) } } .build()!! - .also { + .also { ne -> if (hasJustLaunchedFsi) { - it.notifyFullScreenIntentLaunched() + ne.notifyFullScreenIntentLaunched() } if (isStickyAndNotDemoted) { - assertFalse(it.isDemoted) + assertFalse(ne.isDemoted) } - modifyRanking(it) - .apply { - suppressedVisualEffects?.let { setSuppressedVisualEffects(it) } - visibilityOverride?.let { setVisibilityOverride(it) } - packageSuspended?.let { setSuspended(it) } + modifyRanking(ne) + .also { mrb -> + if (packageSuspended) { + mrb.setSuspended(true) + } + visibilityOverride?.let { mrb.setVisibilityOverride(it) } + suppressedVisualEffects?.let { mrb.setSuppressedVisualEffects(it) } } .build() } @@ -1013,6 +1229,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } protected fun buildBubbleEntry(block: EntryBuilder.() -> Unit = {}) = buildEntry { + isBubble = true canBubble = true hasBubbleMetadata = true run(block) @@ -1024,6 +1241,45 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { run(block) } + private fun assertNoEventsLogged() { + assertNoUiEventLogged() + assertNoSystemEventLogged() + } + + private fun assertNoUiEventLogged() { + assertEquals(0, uiEventLogger.numLogs()) + } + + private fun assertUiEventLogged(uiEventId: UiEventEnum, uid: Int, packageName: String) { + assertEquals(1, uiEventLogger.numLogs()) + + val event = uiEventLogger.get(0) + assertEquals(uiEventId.id, event.eventId) + assertEquals(uid, event.uid) + assertEquals(packageName, event.packageName) + } + + private fun assertNoSystemEventLogged() { + assertEquals(0, eventLog.events.size) + } + + private fun assertSystemEventLogged(number: String, uid: Int, description: String) { + assertEquals(1, eventLog.events.size) + + val event = eventLog.events[0] + assertEquals(0x534e4554, event.tag) + + val value = event.value + assertTrue(value is Array<*>) + + if (value is Array<*>) { + assertEquals(3, value.size) + assertEquals(number, value[0]) + assertEquals(uid, value[1]) + assertEquals(description, value[2]) + } + } + private fun whenAgo(whenAgeMs: Long) = systemClock.currentTimeMillis() - whenAgeMs } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index e7dad6a2908f..912c27d854fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; - import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -30,7 +28,6 @@ import android.app.ActivityManager; import android.app.StatusBarManager; import android.os.PowerManager; import android.os.UserHandle; -import android.os.VibrationEffect; import android.os.Vibrator; import android.testing.AndroidTestingRunner; import android.view.HapticFeedbackConstants; @@ -42,7 +39,6 @@ import com.android.internal.logging.testing.FakeMetricsLogger; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.assist.AssistManager; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; @@ -53,7 +49,6 @@ import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -94,14 +89,12 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private DozeServiceHost mDozeServiceHost; @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private PowerManager mPowerManager; - @Mock private VibratorHelper mVibratorHelper; @Mock private Vibrator mVibrator; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock private Lazy<CameraLauncher> mCameraLauncherLazy; @Mock private UserTracker mUserTracker; @Mock private QSHost mQSHost; @Mock private ActivityStarter mActivityStarter; - private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); CentralSurfacesCommandQueueCallbacks mSbcqCallbacks; @@ -131,15 +124,13 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mNotificationStackScrollLayoutController, mStatusBarHideIconsForBouncerManager, mPowerManager, - mVibratorHelper, Optional.of(mVibrator), new DisableFlagsLogger(), DEFAULT_DISPLAY, mCameraLauncherLazy, mUserTracker, mQSHost, - mActivityStarter, - mFeatureFlags); + mActivityStarter); when(mUserTracker.getUserHandle()).thenReturn( UserHandle.of(ActivityManager.getCurrentUser())); @@ -192,18 +183,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { } @Test - public void vibrateOnNavigationKeyDown_oneWayHapticsDisabled_usesVibrate() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); - - mSbcqCallbacks.vibrateOnNavigationKeyDown(); - - verify(mVibratorHelper).vibrate(VibrationEffect.EFFECT_TICK); - } - - @Test - public void vibrateOnNavigationKeyDown_oneWayHapticsEnabled_usesPerformHapticFeedback() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - + public void vibrateOnNavigationKeyDown_usesPerformHapticFeedback() { mSbcqCallbacks.vibrateOnNavigationKeyDown(); verify(mShadeViewController).performHapticFeedback( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 86a5c52bd983..251718ddf33b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -121,6 +121,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; @@ -176,6 +178,8 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.util.EventLog; +import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.MessageRouterImpl; @@ -322,6 +326,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings(); + private final FakeEventLog mFakeEventLog = new FakeEventLog(); private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @@ -329,6 +334,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final DumpManager mDumpManager = new DumpManager(); private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager); + private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags(); + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -370,7 +377,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, mDeviceProvisionedController, mFakeSystemClock, - mFakeGlobalSettings); + mFakeGlobalSettings, + mFakeEventLog); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); mContext.addMockSystemService(FingerprintManager.class, mock(FingerprintManager.class)); @@ -555,7 +563,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mAlternateBouncerInteractor, mUserTracker, () -> mFingerprintManager, - mActivityStarter + mActivityStarter, + mSceneContainerFlags ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); @@ -1182,7 +1191,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { UserTracker userTracker, DeviceProvisionedController deviceProvisionedController, SystemClock systemClock, - GlobalSettings globalSettings) { + GlobalSettings globalSettings, + EventLog eventLog) { super( powerManager, ambientDisplayConfiguration, @@ -1198,7 +1208,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { userTracker, deviceProvisionedController, systemClock, - globalSettings + globalSettings, + eventLog ); mUseHeadsUp = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java index 0a68406882d4..f71114d92aa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java @@ -16,7 +16,14 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -26,6 +33,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; +import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; @@ -81,4 +89,39 @@ public class LightBarTransitionsControllerTest extends SysuiTestCase { verify(mApplier).applyDarkIntensity(eq(0f)); } + @Test + public void gestureNav_noForceNavButtons_expectNotSupportsIconTint() { + GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class); + doReturn(false).when(observer).areNavigationButtonForcedVisible(); + mLightBarTransitionsController.setNavigationSettingsObserver(observer); + assertFalse(mLightBarTransitionsController.supportsIconTintForNavMode( + NAV_BAR_MODE_GESTURAL)); + } + + @Test + public void gestureNav_forceNavButtons_expectSupportsIconTint() { + GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class); + doReturn(true).when(observer).areNavigationButtonForcedVisible(); + mLightBarTransitionsController.setNavigationSettingsObserver(observer); + assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode( + NAV_BAR_MODE_GESTURAL)); + } + + @Test + public void buttonNav_noForceNavButtons_expectNotSupportsIconTint() { + GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class); + doReturn(false).when(observer).areNavigationButtonForcedVisible(); + mLightBarTransitionsController.setNavigationSettingsObserver(observer); + assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode( + NAV_BAR_MODE_3BUTTON)); + } + + @Test + public void buttonNav_forceNavButtons_expectSupportsIconTint() { + GestureNavigationSettingsObserver observer = mock(GestureNavigationSettingsObserver.class); + doReturn(true).when(observer).areNavigationButtonForcedVisible(); + mLightBarTransitionsController.setNavigationSettingsObserver(observer); + assertTrue(mLightBarTransitionsController.supportsIconTintForNavMode( + NAV_BAR_MODE_3BUTTON)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 1e3197730626..d1423e10ce79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -163,7 +163,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @Mock private InteractionJankMonitor mJankMonitor; private FakePowerRepository mPowerRepository; - private PowerInteractor mPowerInteractor; @Mock private UserTracker mUserTracker; private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -214,7 +213,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { UserHandle.of(ActivityManager.getCurrentUser())); mPowerRepository = new FakePowerRepository(); - mPowerInteractor = PowerInteractorFactory.create( + PowerInteractor mPowerInteractor = PowerInteractorFactory.create( mPowerRepository, new FalsingCollectorFake(), mScreenOffAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 9aafee4770de..6a0375d55a72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.phone -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner @@ -82,14 +80,9 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var handler: Handler - private lateinit var featureFlags: FakeFeatureFlags - @Before fun setUp() { MockitoAnnotations.initMocks(this) - featureFlags = FakeFeatureFlags().apply { - set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) - } controller = UnlockedScreenOffAnimationController( context, wakefulnessLifecycle, @@ -102,7 +95,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { interactionJankMonitor, powerManager, handler = handler, - featureFlags, ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index d1b9b8aae70e..0b87fe8da184 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -58,6 +58,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore; import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; @@ -703,6 +704,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardStateController, mShadeViewController, mStatusBarStateController, + mock(StatusBarIconViewBindingFailureTracker.class), mCommandQueue, mCarrierConfigTracker, new CollapsedStatusBarFragmentLogger( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt index 3dc7de688446..a80238167b85 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt @@ -16,12 +16,28 @@ package com.android.systemui.statusbar.pipeline.mobile.util +import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID /** Fake of [SubscriptionManagerProxy] for easy testing */ class FakeSubscriptionManagerProxy( /** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */ - var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID + var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID, + var activeSubscriptionInfo: SubscriptionInfo? = null ) : SubscriptionManagerProxy { override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId + + override fun isValidSubscriptionId(subId: Int): Boolean { + return subId > -1 + } + + override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? { + return activeSubscriptionInfo + } + + /** Sets the active subscription info. */ + fun setActiveSubscriptionInfo(subId: Int, isEmbedded: Boolean = false) { + activeSubscriptionInfo = + SubscriptionInfo.Builder().setId(subId).setEmbedded(isEmbedded).build() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index d33806e131d5..1250228e2d37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.policy -import com.android.systemui.flags.FakeFeatureFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils @@ -78,13 +77,11 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { private lateinit var view: FrameLayout private lateinit var testableLooper: TestableLooper private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController - private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - featureFlags = FakeFeatureFlags() view = LayoutInflater.from(context) .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout @@ -101,7 +98,6 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() { dozeParameters, screenOffAnimationController, userSwitchDialogController, - featureFlags, uiEventLogger) ViewUtils.attachView(view) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt new file mode 100644 index 000000000000..004f67934e86 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/data/repository/ZenModeRepositoryImplTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.policy.ZenModeController +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ZenModeRepositoryImplTest : SysuiTestCase() { + @Mock lateinit var zenModeController: ZenModeController + + lateinit var underTest: ZenModeRepositoryImpl + + private val testPolicy = NotificationManager.Policy(0, 1, 0) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + underTest = ZenModeRepositoryImpl(zenModeController) + } + + @Test + fun zenMode_reflectsCurrentControllerState() = runTest { + whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + val zenMode by collectLastValue(underTest.zenMode) + assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) + } + + @Test + fun zenMode_updatesWhenControllerStateChanges() = runTest { + val zenMode by collectLastValue(underTest.zenMode) + runCurrent() + whenever(zenModeController.zen).thenReturn(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } + .onZenChanged(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) + } + + @Test + fun policy_reflectsCurrentControllerState() { + runTest { + whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) + val policy by collectLastValue(underTest.consolidatedNotificationPolicy) + assertThat(policy).isEqualTo(testPolicy) + } + } + + @Test + fun policy_updatesWhenControllerStateChanges() = runTest { + val policy by collectLastValue(underTest.consolidatedNotificationPolicy) + runCurrent() + whenever(zenModeController.consolidatedPolicy).thenReturn(testPolicy) + withArgCaptor { Mockito.verify(zenModeController).addCallback(capture()) } + .onConsolidatedPolicyChanged(testPolicy) + assertThat(policy).isEqualTo(testPolicy) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt new file mode 100644 index 000000000000..78e79718e166 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.domain.interactor + +import android.app.NotificationManager.Policy +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.SysUITestComponent +import com.android.SysUITestModule +import com.android.collectLastValue +import com.android.runCurrent +import com.android.runTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepository +import com.android.systemui.user.domain.UserDomainLayerModule +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import org.junit.Test + +@SmallTest +class ZenModeInteractorTest : SysuiTestCase() { + @SysUISingleton + @Component( + modules = + [ + SysUITestModule::class, + UserDomainLayerModule::class, + ] + ) + interface TestComponent : SysUITestComponent<ZenModeInteractor> { + + val repository: FakeZenModeRepository + + @Component.Factory + interface Factory { + fun create(@BindsInstance test: SysuiTestCase): TestComponent + } + } + + private val testComponent: TestComponent = + DaggerZenModeInteractorTest_TestComponent.factory().create(test = this) + + @Test + fun testIsZenModeEnabled_off() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun testIsZenModeEnabled_alarms() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_ALARMS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_importantInterruptions() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_noInterruptions() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + runCurrent() + + assertThat(enabled).isTrue() + } + + @Test + fun testIsZenModeEnabled_unknown() = + testComponent.runTest { + val enabled by collectLastValue(underTest.isZenModeEnabled) + + repository.zenMode.value = 4 // this should fail if we ever add another zen mode type + runCurrent() + + assertThat(enabled).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_noPolicy() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = null + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOffShadeSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.zenMode.value = Settings.Global.ZEN_MODE_OFF + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOnShadeNotSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_STATUS_BAR) + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isFalse() + } + + @Test + fun testAreNotificationsHiddenInShade_zenOnShadeSuppressed() = + testComponent.runTest { + val hidden by collectLastValue(underTest.areNotificationsHiddenInShade) + + repository.consolidatedNotificationPolicy.value = + policyWithSuppressedVisualEffects(Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST) + repository.zenMode.value = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + runCurrent() + + assertThat(hidden).isTrue() + } +} + +fun policyWithSuppressedVisualEffects(suppressedVisualEffects: Int) = + Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + /* suppressedVisualEffects = */ suppressedVisualEffects + ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 1e7e1842fa33..1466d24accee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -21,7 +21,6 @@ import android.os.VibrationAttributes import android.os.VibrationEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -33,7 +32,6 @@ import androidx.core.animation.doOnCancel import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.common.shared.model.ContentDescription @@ -42,9 +40,8 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.dump.DumpManager -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.temporarydisplay.TemporaryViewUiEvent @@ -94,7 +91,6 @@ class ChipbarCoordinatorTest : SysuiTestCase() { private lateinit var fakeExecutor: FakeExecutor private lateinit var uiEventLoggerFake: UiEventLoggerFake private lateinit var uiEventLogger: TemporaryViewUiEventLogger - private val featureFlags = FakeFeatureFlags() @Before fun setUp() { @@ -131,10 +127,8 @@ class ChipbarCoordinatorTest : SysuiTestCase() { fakeWakeLockBuilder, fakeClock, uiEventLogger, - featureFlags ) underTest.start() - featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false) } @Test @@ -494,23 +488,6 @@ class ChipbarCoordinatorTest : SysuiTestCase() { ) } - @Test - fun displayView_oneWayHapticsEnabled_usesPerformHapticFeedback() { - featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true) - val constant: Int = HapticFeedbackConstants.CONFIRM - underTest.displayView( - createChipbarInfo( - Icon.Resource(R.id.check_box, null), - Text.Loaded("text"), - endItem = null, - vibrationEffect = null, - vibrationConstant = constant - ) - ) - - verify(vibratorHelper).performHapticFeedback(any(), eq(constant)) - } - /** Regression test for b/266119467. */ @Test fun displayView_animationFailure_viewsStillBecomeVisible() { @@ -729,14 +706,12 @@ class ChipbarCoordinatorTest : SysuiTestCase() { endItem: ChipbarEndItem?, vibrationEffect: VibrationEffect? = null, allowSwipeToDismiss: Boolean = false, - vibrationConstant: Int = HapticFeedbackConstants.NO_HAPTICS, ): ChipbarInfo { return ChipbarInfo( TintedIcon(startIcon, tint = null), text, endItem, vibrationEffect, - vibrationConstant, allowSwipeToDismiss, windowTitle = WINDOW_TITLE, wakeReason = WAKE_REASON, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 7456e00e948d..8c823b2376c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; @@ -68,7 +67,6 @@ import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.VolumeDialogController; @@ -149,14 +147,13 @@ public class VolumeDialogImplTest extends SysuiTestCase { } }; - private FakeFeatureFlags mFeatureFlags; private int mLongestHideShowAnimationDuration = 250; private FakeSettings mSecureSettings; @Rule public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); - @Before + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); @@ -179,8 +176,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mConfigurationController = new FakeConfigurationController(); - mFeatureFlags = new FakeFeatureFlags(); - mSecureSettings = new FakeSettings(); when(mLazySecureSettings.get()).thenReturn(mSecureSettings); @@ -200,7 +195,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { mPostureController, mTestableLooper.getLooper(), mDumpManager, - mFeatureFlags, mLazySecureSettings); mDialog.init(0, null); State state = createShellState(); @@ -328,7 +322,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test public void testVibrateOnRingerChangedToVibrate() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialSilentState = new State(); initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; @@ -349,30 +342,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testControllerDoesNotVibrateOnRingerChangedToVibrate_OnewayAPI_On() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialSilentState = new State(); - initialSilentState.ringerModeInternal = AudioManager.RINGER_MODE_SILENT; - - final State vibrateState = new State(); - vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - - // change ringer to silent - mDialog.onStateChangedH(initialSilentState); - - // expected: shouldn't call vibrate yet - verify(mVolumeDialogController, never()).vibrate(any()); - - // changed ringer to vibrate - mDialog.onStateChangedH(vibrateState); - - // expected: vibrate method of controller is not used - verify(mVolumeDialogController, never()).vibrate(any()); - } - - @Test public void testNoVibrateOnRingerInitialization() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = -1; @@ -390,29 +360,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testControllerDoesNotVibrateOnRingerInitialization_OnewayAPI_On() { - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = -1; - - // ringer not initialized yet: - mDialog.onStateChangedH(initialUnsetState); - - final State vibrateState = new State(); - vibrateState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - - // changed ringer to vibrate - mDialog.onStateChangedH(vibrateState); - - // shouldn't call vibrate on the controller either - verify(mVolumeDialogController, never()).vibrate(any()); - } - - @Test public void testSelectVibrateFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -426,27 +376,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testSelectVibrateFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerVibrate.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - AudioManager.RINGER_MODE_VIBRATE, false); - } - - @Test public void testSelectMuteFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_NORMAL; mDialog.onStateChangedH(initialUnsetState); @@ -460,27 +392,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - public void testSelectMuteFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = RINGER_MODE_NORMAL; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerMute.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - AudioManager.RINGER_MODE_SILENT, false); - } - - @Test public void testSelectNormalFromDrawer() { assumeHasDrawer(); - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false); final State initialUnsetState = new State(); initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; mDialog.onStateChangedH(initialUnsetState); @@ -493,23 +407,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { AudioManager.RINGER_MODE_NORMAL, false); } - @Test - public void testSelectNormalFromDrawer_OnewayAPI_On() { - assumeHasDrawer(); - - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); - final State initialUnsetState = new State(); - initialUnsetState.ringerModeInternal = AudioManager.RINGER_MODE_VIBRATE; - mDialog.onStateChangedH(initialUnsetState); - - mActiveRinger.performClick(); - mDrawerNormal.performClick(); - - // Make sure we've actually changed the ringer mode. - verify(mVolumeDialogController, times(1)).setRingerMode( - RINGER_MODE_NORMAL, false); - } - /** * Ideally we would look at the ringer ImageView and check its assigned drawable id, but that * API does not exist. So we do the next best thing; we check the cached icon id. @@ -682,7 +579,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { State state = createShellState(); state.ringerModeInternal = ringerMode; - mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); mDialog.onStateChangedH(state); mDialog.show(SHOW_REASON_UNKNOWN); 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 a5806001dd43..aa5f987b22a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -93,7 +93,6 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository; -import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.dump.DumpManager; @@ -112,7 +111,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.scene.FakeWindowRootViewComponent; import com.android.systemui.scene.SceneTestUtils; @@ -125,10 +123,11 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; -import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractorImpl; +import com.android.systemui.shade.domain.interactor.ShadeInteractorLegacyImpl; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.NotificationEntryHelper; @@ -163,6 +162,7 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.user.domain.interactor.UserSwitcherInteractor; +import com.android.systemui.util.FakeEventLog; import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.time.SystemClock; import com.android.wm.shell.ShellTaskOrganizer; @@ -249,8 +249,6 @@ public class BubblesTest extends SysuiTestCase { private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private AuthController mAuthController; - @Mock - private ShadeExpansionStateManager mShadeExpansionStateManager; private SysUiState mSysUiState; private boolean mSysUiStateBubblesExpanded; @@ -340,8 +338,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private Icon mAppBubbleIcon; - private SceneTestUtils mUtils = new SceneTestUtils(this); - private TestScope mTestScope = mUtils.getTestScope(); + private final SceneTestUtils mUtils = new SceneTestUtils(this); + private final TestScope mTestScope = mUtils.getTestScope(); private ShadeInteractor mShadeInteractor; private ShellTaskOrganizer mShellTaskOrganizer; private TaskViewTransitions mTaskViewTransitions; @@ -352,7 +350,7 @@ public class BubblesTest extends SysuiTestCase { private TestableLooper mTestableLooper; - private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private final FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private UserHandle mUser0; @@ -388,12 +386,11 @@ public class BubblesTest extends SysuiTestCase { FakeKeyguardRepository keyguardRepository = new FakeKeyguardRepository(); FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeShadeRepository shadeRepository = new FakeShadeRepository(); - FakePowerRepository powerRepository = new FakePowerRepository(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); PowerInteractor powerInteractor = new PowerInteractor( - powerRepository, - new FalsingCollectorFake(), + mUtils.getPowerRepository(), + mUtils.falsingCollector(), mock(ScreenOffAnimationController.class), mStatusBarStateController); @@ -402,7 +399,7 @@ public class BubblesTest extends SysuiTestCase { new SceneContainerRepository( mTestScope.getBackgroundScope(), mUtils.fakeSceneContainerConfig()), - powerRepository, + powerInteractor, mock(SceneLogger.class)); FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); @@ -441,7 +438,7 @@ public class BubblesTest extends SysuiTestCase { new InWindowLauncherUnlockAnimationRepository(), mTestScope.getBackgroundScope(), keyguardTransitionInteractor, - () -> new FakeKeyguardSurfaceBehindRepository(), + FakeKeyguardSurfaceBehindRepository::new, mock(ActivityManagerWrapper.class) ) ); @@ -460,23 +457,24 @@ public class BubblesTest extends SysuiTestCase { new ResourcesSplitShadeStateController(); mShadeInteractor = - new ShadeInteractor( + new ShadeInteractorImpl( mTestScope.getBackgroundScope(), deviceProvisioningRepository, new FakeDisableFlagsRepository(), mDozeParameters, - sceneContainerFlags, - () -> sceneInteractor, keyguardRepository, keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), mock(UserSwitcherInteractor.class), - new SharedNotificationContainerInteractor( - configurationRepository, - mContext, - splitShadeStateController), - new FakeShadeRepository() + new ShadeInteractorLegacyImpl( + mTestScope.getBackgroundScope(), keyguardRepository, + new SharedNotificationContainerInteractor( + configurationRepository, + mContext, + splitShadeStateController), + shadeRepository + ) ); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl( @@ -546,7 +544,8 @@ public class BubblesTest extends SysuiTestCase { mock(UserTracker.class), mock(DeviceProvisionedController.class), mock(SystemClock.class), - fakeGlobalSettings + fakeGlobalSettings, + new FakeEventLog() ); mShellTaskOrganizer = new ShellTaskOrganizer(mock(ShellInit.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java index 975555c1a46b..c9964c233d44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.EventLog; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.time.SystemClock; @@ -52,7 +53,8 @@ public class TestableNotificationInterruptStateProviderImpl UserTracker userTracker, DeviceProvisionedController deviceProvisionedController, SystemClock systemClock, - GlobalSettings globalSettings) { + GlobalSettings globalSettings, + EventLog eventLog) { super( powerManager, ambientDisplayConfiguration, @@ -68,7 +70,8 @@ public class TestableNotificationInterruptStateProviderImpl userTracker, deviceProvisionedController, systemClock, - globalSettings); + globalSettings, + eventLog); mUseHeadsUp = true; } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 6e9363b744ab..45ded7ffcc8c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -60,6 +60,12 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 + override val minPasswordLength: Int = 4 + + private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false) + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + _isPinEnhancedPrivacyEnabled.asStateFlow() + private var failedAttemptCount = 0 private var throttlingEndTimestamp = 0L private var credentialOverride: List<Any>? = null @@ -138,6 +144,10 @@ class FakeAuthenticationRepository( } } + fun setPinEnhancedPrivacyEnabled(isEnabled: Boolean) { + _isPinEnhancedPrivacyEnabled.value = isEnabled + } + private fun getExpectedCredential(securityMode: SecurityMode): List<Any> { return when (val credentialType = getCurrentCredentialType(securityMode)) { LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN @@ -170,6 +180,7 @@ class FakeAuthenticationRepository( is AuthenticationMethodModel.Password -> SecurityMode.Password is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern is AuthenticationMethodModel.None -> SecurityMode.None + is AuthenticationMethodModel.Sim -> SecurityMode.SimPin } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index 0c5e43809fab..005cac490d89 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -19,10 +19,15 @@ package com.android.systemui.biometrics.data.repository import android.hardware.biometrics.SensorLocationInternal import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { +@SysUISingleton +class FakeFingerprintPropertyRepository @Inject constructor() : FingerprintPropertyRepository { private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1) override val sensorId = _sensorId.asStateFlow() @@ -50,4 +55,29 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { _sensorType.value = sensorType _sensorLocations.value = sensorLocations } + + /** setProperties as if the device supports UDFPS_OPTICAL. */ + fun supportsUdfps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.UDFPS_OPTICAL, + sensorLocations = emptyMap(), + ) + } + + /** setProperties as if the device supports the rear fingerprint sensor. */ + fun supportsRearFps() { + setProperties( + sensorId = 0, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = emptyMap(), + ) + } +} + +@Module +interface FakeFingerprintPropertyRepositoryModule { + @Binds fun bindFake(fake: FakeFingerprintPropertyRepository): FingerprintPropertyRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt new file mode 100644 index 000000000000..890e69dced0b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.data.repository + +import android.telephony.SubscriptionInfo +import com.android.systemui.bouncer.data.model.SimPukInputModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Fakes the SimBouncerRepository. */ +class FakeSimBouncerRepository : SimBouncerRepository { + private val _subscriptionId: MutableStateFlow<Int> = MutableStateFlow(-1) + override val subscriptionId: StateFlow<Int> = _subscriptionId + private val _activeSubscriptionInfo: MutableStateFlow<SubscriptionInfo?> = + MutableStateFlow(null) + override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> = _activeSubscriptionInfo + private val _isLockedEsim: MutableStateFlow<Boolean?> = MutableStateFlow(null) + override val isLockedEsim: StateFlow<Boolean?> = _isLockedEsim + private val _isSimPukLocked: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val isSimPukLocked: StateFlow<Boolean> = _isSimPukLocked + private val _errorDialogMessage: MutableStateFlow<String?> = MutableStateFlow(null) + override val errorDialogMessage: StateFlow<String?> = _errorDialogMessage + private var _simPukInputModel = SimPukInputModel() + override val simPukInputModel: SimPukInputModel + get() = _simPukInputModel + + fun setSubscriptionId(subId: Int) { + _subscriptionId.value = subId + } + + fun setActiveSubscriptionInfo(subscriptioninfo: SubscriptionInfo) { + _activeSubscriptionInfo.value = subscriptioninfo + } + + fun setLockedEsim(isLockedEsim: Boolean) { + _isLockedEsim.value = isLockedEsim + } + + fun setSimPukLocked(isSimPukLocked: Boolean) { + _isSimPukLocked.value = isSimPukLocked + } + + fun setErrorDialogMessage(msg: String?) { + _errorDialogMessage.value = msg + } + + override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) { + _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin) + } + + override fun setSimVerificationErrorMessage(msg: String?) { + _errorDialogMessage.value = msg + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt index 44286b715abb..8ff04a63802a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt @@ -15,17 +15,23 @@ package com.android.systemui.deviceentry.data +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepositoryModule import com.android.systemui.keyguard.data.repository.FakeTrustRepositoryModule import dagger.Module @Module( includes = [ + FakeBiometricSettingsRepositoryModule::class, FakeDeviceEntryRepositoryModule::class, - FakeTrustRepositoryModule::class, FakeDeviceEntryFaceAuthRepositoryModule::class, + FakeDeviceEntryFingerprintAuthRepositoryModule::class, + FakeFingerprintPropertyRepositoryModule::class, + FakeTrustRepositoryModule::class, ] ) object FakeDeviceEntryDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index 852611230623..df31a12b8415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -18,13 +18,18 @@ package com.android.systemui.keyguard.data.repository import com.android.internal.widget.LockPatternUtils +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map -class FakeBiometricSettingsRepository : BiometricSettingsRepository { +@SysUISingleton +class FakeBiometricSettingsRepository @Inject constructor() : BiometricSettingsRepository { private val _isFingerprintEnrolledAndEnabled = MutableStateFlow(false) override val isFingerprintEnrolledAndEnabled: StateFlow<Boolean> get() = _isFingerprintEnrolledAndEnabled @@ -97,3 +102,8 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository { } } } + +@Module +interface FakeBiometricSettingsRepositoryModule { + @Binds fun bindFake(fake: FakeBiometricSettingsRepository): BiometricSettingsRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt index 38791caf5bfc..c9160efb75a4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -17,14 +17,20 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus +import dagger.Binds +import dagger.Module +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { +@SysUISingleton +class FakeDeviceEntryFingerprintAuthRepository @Inject constructor() : + DeviceEntryFingerprintAuthRepository { private val _isLockedOut = MutableStateFlow(false) override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow() fun setLockedOut(lockedOut: Boolean) { @@ -52,3 +58,11 @@ class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepos _authenticationStatus.value = status } } + +@Module +interface FakeDeviceEntryFingerprintAuthRepositoryModule { + @Binds + fun bindFake( + fake: FakeDeviceEntryFingerprintAuthRepository + ): DeviceEntryFingerprintAuthRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index b90ad8cd8745..a94ca291298d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -40,7 +40,7 @@ import kotlinx.coroutines.test.runCurrent class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository { private val _transitions = - MutableSharedFlow<TransitionStep>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + MutableSharedFlow<TransitionStep>(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val transitions: SharedFlow<TransitionStep> = _transitions init { @@ -130,7 +130,7 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio * only a FINISHED step, override [validateStep]. */ suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) { - _transitions.replayCache.getOrNull(0)?.let { lastStep -> + _transitions.replayCache.last().let { lastStep -> if ( validateStep && step.transitionState == TransitionState.FINISHED && @@ -151,6 +151,17 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio _transitions.emit(step) } + suspend fun sendTransitionSteps( + steps: List<TransitionStep>, + testScope: TestScope, + validateStep: Boolean = true + ) { + steps.forEach { + sendTransitionStep(it, validateStep = validateStep) + testScope.testScheduler.runCurrent() + } + } + override fun startTransition(info: TransitionInfo): UUID? { return if (info.animator == null) UUID.randomUUID() else null } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt index 1efa74b0551a..62765d10486c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -20,7 +20,6 @@ import android.os.UserHandle class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { - var handleResult: Boolean = false var policyResult: DisabledByPolicyInteractor.PolicyResult = DisabledByPolicyInteractor.PolicyResult.TileEnabled @@ -31,5 +30,9 @@ class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { override fun handlePolicyResult( policyResult: DisabledByPolicyInteractor.PolicyResult - ): Boolean = handleResult + ): Boolean = + when (policyResult) { + is DisabledByPolicyInteractor.PolicyResult.TileEnabled -> false + is DisabledByPolicyInteractor.PolicyResult.TileDisabled -> true + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 2b3330f3a33b..3fcf8a93dc87 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -17,16 +17,21 @@ package com.android.systemui.qs.tiles.base.interactor import android.os.UserHandle -import javax.annotation.CheckReturnValue import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flatMapLatest -class FakeQSTileDataInteractor<T>( - private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE), - private val availabilityFlow: MutableSharedFlow<Boolean> = - MutableSharedFlow(replay = Int.MAX_VALUE), -) : QSTileDataInteractor<T> { +class FakeQSTileDataInteractor<T> : QSTileDataInteractor<T> { + + private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = 1) + val dataSubscriptionCount + get() = dataFlow.subscriptionCount + private val availabilityFlow: MutableSharedFlow<Boolean> = MutableSharedFlow(replay = 1) + val availabilitySubscriptionCount + get() = availabilityFlow.subscriptionCount + + private val mutableTriggers = mutableListOf<DataUpdateTrigger>() + val triggers: List<DataUpdateTrigger> = mutableTriggers private val mutableDataRequests = mutableListOf<DataRequest>() val dataRequests: List<DataRequest> = mutableDataRequests @@ -34,14 +39,17 @@ class FakeQSTileDataInteractor<T>( private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>() val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests - @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data) + suspend fun emitData(data: T): Unit = dataFlow.emit(data) fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable) suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable) override fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<T> { mutableDataRequests.add(DataRequest(user)) - return triggers.flatMapLatest { dataFlow } + return triggers.flatMapLatest { + mutableTriggers.add(it) + dataFlow + } } override fun availability(user: UserHandle): Flow<Boolean> { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt index de72a7dc30d7..d231d63a3906 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt @@ -24,6 +24,8 @@ class FakeQSTileConfigProvider : QSTileConfigProvider { override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec) + override fun hasConfig(tileSpec: String): Boolean = configs.containsKey(tileSpec) + fun putConfig(tileSpec: TileSpec, config: QSTileConfig) { configs[tileSpec.spec] = config } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt new file mode 100644 index 000000000000..2902c3f65a16 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.adapter + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import com.android.systemui.qs.ui.adapter.QSSceneAdapter +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull + +class FakeQSSceneAdapter( + private val inflateDelegate: suspend (Context, ViewGroup?) -> View, +) : QSSceneAdapter { + private val _customizing = MutableStateFlow(false) + override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow() + + private val _view = MutableStateFlow<View?>(null) + override val qsView: Flow<View> = _view.filterNotNull() + + private val _state = MutableStateFlow<QSSceneAdapter.State?>(null) + val state = _state.filterNotNull() + + override suspend fun inflate(context: Context, parent: ViewGroup?) { + _view.value = inflateDelegate(context, parent) + } + + override fun setState(state: QSSceneAdapter.State) { + if (_view.value != null) { + _state.value = state + } + } + + fun setCustomizing(value: Boolean) { + _customizing.value = value + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 72cc08f03dac..29e73b548b0b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -23,6 +23,10 @@ import android.content.pm.UserInfo import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.telecom.TelecomManager +import android.telephony.PinResult +import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS +import android.telephony.TelephonyManager +import android.telephony.euicc.EuiccManager import com.android.internal.logging.MetricsLogger import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.SysuiTestCase @@ -32,9 +36,11 @@ import com.android.systemui.authentication.domain.interactor.AuthenticationInter import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor import com.android.systemui.bouncer.domain.interactor.BouncerInteractor import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory +import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake @@ -61,7 +67,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.data.repository.PowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -69,6 +78,8 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.data.repository.TelephonyRepository @@ -85,6 +96,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.currentTime +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito /** * Utilities for creating scene container framework related repositories, interactors, and @@ -123,9 +137,33 @@ class SceneTestUtils( } val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() } + val bouncerRepository = BouncerRepository(featureFlags) val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() } val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() } val powerRepository: FakePowerRepository by lazy { FakePowerRepository() } + val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() } + val telephonyManager: TelephonyManager = + Mockito.mock(TelephonyManager::class.java).apply { + whenever(createForSubscriptionId(anyInt())).thenReturn(this) + whenever(supplyIccLockPin(anyString())) + .thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3)) + } + val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy { + FakeMobileConnectionsRepository(mock(), mock()) + } + + val simBouncerInteractor = + SimBouncerInteractor( + applicationContext = context, + backgroundDispatcher = testDispatcher, + applicationScope = applicationScope(), + repository = simBouncerRepository, + telephonyManager = telephonyManager, + resources = context.resources, + keyguardUpdateMonitor = mock(), + euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager, + mobileConnectionsRepository = mobileConnectionsRepository, + ) val userRepository: UserRepository by lazy { FakeUserRepository().apply { @@ -137,6 +175,7 @@ class SceneTestUtils( private val falsingCollectorFake: FalsingCollector by lazy { FalsingCollectorFake() } private var falsingInteractor: FalsingInteractor? = null + private var powerInteractor: PowerInteractor? = null fun fakeSceneContainerRepository( containerConfig: SceneContainerConfig = fakeSceneContainerConfig(), @@ -159,7 +198,7 @@ class SceneTestUtils( return SceneInteractor( applicationScope = applicationScope(), repository = repository, - powerRepository = powerRepository, + powerInteractor = powerInteractor(), logger = mock(), ) } @@ -223,10 +262,12 @@ class SceneTestUtils( return BouncerInteractor( applicationScope = applicationScope(), applicationContext = context, - repository = BouncerRepository(featureFlags), + repository = bouncerRepository, authenticationInteractor = authenticationInteractor, flags = sceneContainerFlags, falsingInteractor = falsingInteractor(), + powerInteractor = powerInteractor(), + simBouncerInteractor = simBouncerInteractor, ) } @@ -247,6 +288,7 @@ class SceneTestUtils( users = flowOf(users), userSwitcherMenu = flowOf(createMenuActions()), actionButtonInteractor = actionButtonInteractor, + simBouncerInteractor = simBouncerInteractor, ) } @@ -264,6 +306,22 @@ class SceneTestUtils( return falsingCollectorFake } + fun powerInteractor( + repository: PowerRepository = powerRepository, + falsingCollector: FalsingCollector = falsingCollector(), + screenOffAnimationController: ScreenOffAnimationController = mock(), + statusBarStateController: StatusBarStateController = mock(), + ): PowerInteractor { + return powerInteractor + ?: PowerInteractor( + repository = repository, + falsingCollector = falsingCollector, + screenOffAnimationController = screenOffAnimationController, + statusBarStateController = statusBarStateController, + ) + .also { powerInteractor = it } + } + private fun applicationScope(): CoroutineScope { return testScope.backgroundScope } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 02318abe8488..92ec4f22001b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -66,6 +66,15 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { _legacyIsQsExpanded.value = legacyIsQsExpanded } + private val _legacyExpandImmediate = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") + override val legacyExpandImmediate = _legacyExpandImmediate + + @Deprecated("Use ShadeInteractor instead") + override fun setLegacyExpandImmediate(legacyExpandImmediate: Boolean) { + _legacyExpandImmediate.value = legacyExpandImmediate + } + @Deprecated("Use ShadeInteractor instead") override fun setLegacyExpandedOrAwaitingInputTransfer( legacyExpandedOrAwaitingInputTransfer: Boolean @@ -88,6 +97,14 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { legacyLockscreenShadeTracking.value = tracking } + private val _legacyQsFullscreen = MutableStateFlow(false) + @Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen = _legacyQsFullscreen + + @Deprecated("Use ShadeInteractor instead") + override fun setLegacyQsFullscreen(legacyQsFullscreen: Boolean) { + _legacyQsFullscreen.value = legacyQsFullscreen + } + fun setShadeModel(model: ShadeModel) { _shadeModel.value = model } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index a9c8ec7dcb7d..a9c8ec7dcb7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index cce038f4ffc1..cce038f4ffc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt index 5aece1bbbd31..16dab40d6edc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/FakeStatusBarPolicyDataLayerModule.kt @@ -16,7 +16,14 @@ package com.android.systemui.statusbar.policy.data import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepositoryModule +import com.android.systemui.statusbar.policy.data.repository.FakeZenModeRepositoryModule import dagger.Module -@Module(includes = [FakeDeviceProvisioningRepositoryModule::class]) +@Module( + includes = + [ + FakeDeviceProvisioningRepositoryModule::class, + FakeZenModeRepositoryModule::class, + ] +) object FakeStatusBarPolicyDataLayerModule diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt new file mode 100644 index 000000000000..405993073b68 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/data/repository/FakeZenModeRepository.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy.data.repository + +import android.app.NotificationManager +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class FakeZenModeRepository @Inject constructor() : ZenModeRepository { + override val zenMode: MutableStateFlow<Int> = MutableStateFlow(Settings.Global.ZEN_MODE_OFF) + override val consolidatedNotificationPolicy: MutableStateFlow<NotificationManager.Policy?> = + MutableStateFlow( + NotificationManager.Policy( + /* priorityCategories = */ 0, + /* priorityCallSenders = */ 0, + /* priorityMessageSenders = */ 0, + ) + ) +} + +@Module +interface FakeZenModeRepositoryModule { + @Binds fun bindFake(fake: FakeZenModeRepository): ZenModeRepository +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt new file mode 100644 index 000000000000..ea2eeabf72eb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeEventLog.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util + +/** A fake [com.android.systemui.util.EventLog] for tests. */ +class FakeEventLog : EventLog { + data class Event(val tag: Int, val value: Any) + + private val _events: MutableList<Event> = mutableListOf() + val events: List<Event> + get() = _events + + fun clear() { + _events.clear() + } + + override fun writeEvent(tag: Int, value: Int): Int { + _events.add(Event(tag, value)) + return 1 + } + + override fun writeEvent(tag: Int, value: Long): Int { + _events.add(Event(tag, value)) + return 1 + } + + override fun writeEvent(tag: Int, value: Float): Int { + _events.add(Event(tag, value)) + return 1 + } + + override fun writeEvent(tag: Int, value: String): Int { + _events.add(Event(tag, value)) + return 1 + } + + override fun writeEvent(tag: Int, vararg values: Any): Int { + _events.add(Event(tag, values)) + return 1 + } +} diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 4c224fb33b26..fb521e11c083 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -359,7 +359,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_doesApplyLater() throws IOException { + public void testUpdateWallpaperComponent_systemAndLock() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent, /* which */ FLAG_LOCK | FLAG_SYSTEM); @@ -377,7 +377,7 @@ public class WallpaperBackupAgentTest { } @Test - public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen() + public void testUpdateWallpaperComponent_systemOnly() throws IOException { mWallpaperBackupAgent.mIsDeviceInRestore = true; @@ -617,7 +617,7 @@ public class WallpaperBackupAgentTest { mWallpaperBackupAgent.onRestoreFinished(); - // wallpaper will be applied to home & lock screen, a success for both screens in expected + // wallpaper will be applied to home & lock screen, a success for both screens is expected DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); assertThat(result).isNotNull(); diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index ec12d2171a93..fc4ed1d4d527 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -28,9 +28,8 @@ java_library { name: "ravenwood-junit", srcs: ["junit-src/**/*.java"], libs: [ + "framework-minus-apex.ravenwood", "junit", ], - sdk_version: "core_current", - host_supported: true, visibility: ["//visibility:public"], } diff --git a/ravenwood/README-ravenwood+mockito.md b/ravenwood/README-ravenwood+mockito.md new file mode 100644 index 000000000000..6adb61441bb1 --- /dev/null +++ b/ravenwood/README-ravenwood+mockito.md @@ -0,0 +1,24 @@ +# Ravenwood and Mockito + +Last update: 2023-11-13 + +- As of 2023-11-13, `external/mockito` is based on version 2.x. +- Mockito didn't support static mocking before 3.4.0. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +- Latest Mockito is 5.*. According to https://github.com/mockito/mockito: + `Mockito 3 does not introduce any breaking API changes, but now requires Java 8 over Java 6 for Mockito 2. Mockito 4 removes deprecated API. Mockito 5 switches the default mockmaker to mockito-inline, and now requires Java 11.` + +- Mockito now supports Android natively. + See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.1 + - But it's unclear at this point to omakoto@ how the `mockito-android` module is built. + +- Potential plan: + - Ideal option: + - If we can update `external/mockito`, that'd be great, but it may not work because + Mockito has removed the deprecated APIs. + - Second option: + - Import the latest mockito as `external/mockito-new`, and require ravenwood + to use this one. + - The latest mockito needs be exposed to all of 1) device tests, 2) host tests, and 3) ravenwood tests. + - This probably will require the latest `bytebuddy` and `objenesis`.
\ No newline at end of file diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java index 76964a72dd3e..7dc197e6bdfd 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodClassLoadHook.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.TYPE; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java index ddf65dc2c5ac..1d315798d647 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java index d7ef7f55921b..d2c77c1b8566 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodKeepWholeClass.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; @@ -35,5 +35,5 @@ import java.lang.annotation.Target; */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) -public @interface RavenwoodWholeClassKeep { +public @interface RavenwoodKeepWholeClass { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java index 8cdc1ff91081..4b9cf85e16fa 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodNativeSubstitutionClass.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.TYPE; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java index 759c918c4a66..6727327c99be 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java index 5a0a8f4f5aae..a920f63152fb 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.METHOD; @@ -31,8 +31,5 @@ import java.lang.annotation.Target; */ @Target({METHOD}) @Retention(RetentionPolicy.CLASS) -public @interface RavenwoodSubstitute { - // TODO We should add "_host" as default. We're not doing it yet, because extractign the default - // value with ASM doesn't seem trivial. (? not sure.) - String suffix(); +public @interface RavenwoodReplace { } diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java index de3dd0465c59..a234a9b6fc7c 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.ravenwood.annotations; +package android.ravenwood.annotation; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.METHOD; diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index 48c0a2de8f86..692d598ac2bb 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -76,21 +76,13 @@ class android.util.Patterns stubclass class android.util.UtilConfig stubclass # Internals -class com.android.internal.util.ArrayUtils stubclass - method newUnpaddedByteArray (I)[B @newUnpaddedByteArray$ravenwood - method newUnpaddedCharArray (I)[C @newUnpaddedCharArray$ravenwood - method newUnpaddedIntArray (I)[I @newUnpaddedIntArray$ravenwood - method newUnpaddedBooleanArray (I)[Z @newUnpaddedBooleanArray$ravenwood - method newUnpaddedLongArray (I)[J @newUnpaddedLongArray$ravenwood - method newUnpaddedFloatArray (I)[F @newUnpaddedFloatArray$ravenwood - method newUnpaddedObjectArray (I)[Ljava/lang/Object; @newUnpaddedObjectArray$ravenwood - method newUnpaddedArray (Ljava/lang/Class;I)[Ljava/lang/Object; @newUnpaddedArray$ravenwood - class com.android.internal.util.GrowingArrayUtils stubclass class com.android.internal.util.LineBreakBufferedWriter stubclass class com.android.internal.util.Preconditions stubclass class com.android.internal.util.StringPool stubclass +class com.android.internal.os.SomeArgs stubclass + # Parcel class android.os.Parcel stubclass method writeException (Ljava/lang/Exception;)V @writeException$ravenwood @@ -102,14 +94,16 @@ class android.os.ParcelFormatException stubclass class android.os.BadParcelableException stubclass class android.os.BadTypeParcelableException stubclass -# Binder: just enough to construct, no further functionality -class android.os.Binder stub - method <init> ()V stub - method <init> (Ljava/lang/String;)V stub - method isDirectlyHandlingTransaction ()Z stub - method isDirectlyHandlingTransactionNative ()Z @isDirectlyHandlingTransactionNative$ravenwood - method getNativeBBinderHolder ()J @getNativeBBinderHolder$ravenwood +# Binder +class android.os.DeadObjectException stubclass +class android.os.DeadSystemException stubclass +class android.os.RemoteException stubclass +class android.os.TransactionTooLargeException stubclass # Containers class android.os.BaseBundle stubclass class android.os.Bundle stubclass + +# Misc +class android.os.PatternMatcher stubclass +class android.os.ParcelUuid stubclass diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index a6b3f668efa6..bffd0cdc9412 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -16,6 +16,7 @@ package android.platform.test.ravenwood; +import android.os.Process; import android.platform.test.annotations.IgnoreUnderRavenwood; import org.junit.Assume; @@ -23,6 +24,8 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; +import java.util.concurrent.atomic.AtomicInteger; + /** * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY * QUESTIONS ABOUT IT. @@ -30,20 +33,84 @@ import org.junit.runners.model.Statement; * @hide */ public class RavenwoodRule implements TestRule { + private static AtomicInteger sNextPid = new AtomicInteger(100); + + /** + * Unless the test author requests differently, run as "nobody", and give each collection of + * tests its own unique PID. + */ + private int mUid = android.os.Process.NOBODY_UID; + private int mPid = sNextPid.getAndIncrement(); + + public RavenwoodRule() { + } + + public static class Builder { + private RavenwoodRule mRule = new RavenwoodRule(); + + public Builder() { + } + + /** + * Configure the identity of this process to be the system UID for the duration of the + * test. Has no effect under non-Ravenwood environments. + */ + public Builder setProcessSystem() { + mRule.mUid = android.os.Process.SYSTEM_UID; + return this; + } + + /** + * Configure the identity of this process to be an app UID for the duration of the + * test. Has no effect under non-Ravenwood environments. + */ + public Builder setProcessApp() { + mRule.mUid = android.os.Process.FIRST_APPLICATION_UID; + return this; + } + + public RavenwoodRule build() { + return mRule; + } + } + + /** + * Return if the current process is running under a Ravenwood test environment. + */ public boolean isUnderRavenwood() { // TODO: give ourselves a better environment signal return System.getProperty("java.class.path").contains("ravenwood"); } + private void init() { + android.os.Process.init$ravenwood(mUid, mPid); + android.os.Binder.init$ravenwood(); + } + + private void reset() { + android.os.Process.reset$ravenwood(); + android.os.Binder.reset$ravenwood(); + } + @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + final boolean isUnderRavenwood = isUnderRavenwood(); if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) { - Assume.assumeFalse(isUnderRavenwood()); + Assume.assumeFalse(isUnderRavenwood); + } + if (isUnderRavenwood) { + init(); + } + try { + base.evaluate(); + } finally { + if (isUnderRavenwood) { + reset(); + } } - base.evaluate(); } }; } diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp new file mode 100644 index 000000000000..4135022d2bc9 --- /dev/null +++ b/ravenwood/mockito/Android.bp @@ -0,0 +1,72 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +// Ravenwood tests run on the hostside, so we need mockit of the host variant. +// But we need to use it in modules of the android variant, so we "wash" the variant with it. +java_host_for_device { + name: "mockito_ravenwood", + libs: [ + "mockito", + "objenesis", + ], +} + +android_ravenwood_test { + name: "RavenwoodMockitoTest", + + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit", + "truth", + + "mockito_ravenwood", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + auto_gen_config: true, +} + +android_test { + name: "RavenwoodMockitoTest_device", + + srcs: [ + "test/**/*.java", + ], + static_libs: [ + "junit", + "truth", + + "androidx.test.rules", + + "ravenwood-junit", + + "mockito-target-extended-minus-junit4", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + // Required by mockito + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + test_suites: [ + "device-tests", + ], + optimize: { + enabled: false, + }, +} diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/mockito/AndroidManifest.xml new file mode 100644 index 000000000000..15f0a2934b5f --- /dev/null +++ b/ravenwood/mockito/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.ravenwood.mockitotest"> + + <application android:debuggable="true" > + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.ravenwood.mockitotest" + /> +</manifest> diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml new file mode 100644 index 000000000000..96bc2752fe95 --- /dev/null +++ b/ravenwood/mockito/AndroidTest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks Services Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksMockingServicesTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.ravenwood.mockitotest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + </test> +</configuration> diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java new file mode 100644 index 000000000000..36fa3dd94e29 --- /dev/null +++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ravenwood.mockito; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import org.junit.Rule; +import org.junit.Test; + +public class RavenwoodMockitoTest { + @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + +// Use this to mock static methods, which isn't supported by mockito 2. +// Mockito supports static mocking since 3.4.0: +// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48 + +// private MockitoSession mMockingSession; +// +// @Before +// public void setUp() { +// mMockingSession = mockitoSession() +// .strictness(Strictness.LENIENT) +// .mockStatic(RavenwoodMockitoTest.class) +// .startMocking(); +// } +// +// @After +// public void tearDown() { +// if (mMockingSession != null) { +// mMockingSession.finishMocking(); +// } +// } + + @Test + public void testMockJdkClass() { + Process object = mock(Process.class); + + when(object.exitValue()).thenReturn(42); + + assertThat(object.exitValue()).isEqualTo(42); + } + + /* + - Intent can't be mocked because of the dependency to `org.xmlpull.v1.XmlPullParser`. + (The error says "Mockito can only mock non-private & non-final classes", but that's likely a + red-herring.) + +STACKTRACE: +org.mockito.exceptions.base.MockitoException: +Mockito cannot mock this class: class android.content.Intent. + + : + +Underlying exception : java.lang.IllegalArgumentException: Could not create type + at com.android.ravenwood.mockito.RavenwoodMockitoTest.testMockAndroidClass1 + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + + : + +Caused by: java.lang.ClassNotFoundException: org.xmlpull.v1.XmlPullParser + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) + ... 54 more + */ + @Test + @IgnoreUnderRavenwood + public void testMockAndroidClass1() { + Intent object = mock(Intent.class); + + when(object.getAction()).thenReturn("ACTION_RAVENWOOD"); + + assertThat(object.getAction()).isEqualTo("ACTION_RAVENWOOD"); + } + + @Test + public void testMockAndroidClass2() { + Context object = mock(Context.class); + + when(object.getPackageName()).thenReturn("android"); + + assertThat(object.getPackageName()).isEqualTo("android"); + } +} diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 0811f90a504c..776a19a68a31 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,2 +1,9 @@ # Only classes listed here can use the Ravenwood annotations. +com.android.internal.util.ArrayUtils + +android.os.Binder +android.os.Binder$IdentitySupplier +android.os.IBinder +android.os.Process +android.os.SystemClock diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt index 6e1384f368b8..4b07ef6e35a8 100644 --- a/ravenwood/ravenwood-standard-options.txt +++ b/ravenwood/ravenwood-standard-options.txt @@ -16,22 +16,22 @@ # Standard annotations. # Note, each line is a single argument, so we need newlines after each `--xxx-annotation`. --keep-annotation - android.ravenwood.annotations.RavenwoodKeep + android.ravenwood.annotation.RavenwoodKeep --keep-class-annotation - android.ravenwood.annotations.RavenwoodWholeClassKeep + android.ravenwood.annotation.RavenwoodKeepWholeClass --throw-annotation - android.ravenwood.annotations.RavenwoodThrow + android.ravenwood.annotation.RavenwoodThrow --remove-annotation - android.ravenwood.annotations.RavenwoodRemove + android.ravenwood.annotation.RavenwoodRemove --substitute-annotation - android.ravenwood.annotations.RavenwoodSubstitute + android.ravenwood.annotation.RavenwoodReplace --native-substitute-annotation - android.ravenwood.annotations.RavenwoodNativeSubstitutionClass + android.ravenwood.annotation.RavenwoodNativeSubstitutionClass --class-load-hook-annotation - android.ravenwood.annotations.RavenwoodClassLoadHook + android.ravenwood.annotation.RavenwoodClassLoadHook diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 6d82b749fda3..f73b00c1fcce 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -26,7 +26,6 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILIT import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT; import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; -import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityInteractionClient.CALL_STACK; import static android.view.accessibility.AccessibilityInteractionClient.IGNORE_CALL_STACK; @@ -69,7 +68,6 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.Trace; import android.provider.Settings; import android.util.Pair; import android.util.Slog; @@ -93,7 +91,6 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; -import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; @@ -101,7 +98,6 @@ import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; -import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; @@ -1993,20 +1989,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private void createImeSessionInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { - try { - if (svcClientTracingEnabled()) { - logTraceSvcClient("createImeSession", ""); - } - AccessibilityCallback callback = new AccessibilityCallback(); - listener.createImeSession(callback); - } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error requesting IME session from " + mService, re); - } - } + protected void createImeSessionInternal() { } private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session, @@ -2658,21 +2641,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } - private static final class AccessibilityCallback - extends IAccessibilityInputMethodSessionCallback.Stub { - @Override - public void sessionCreated(IAccessibilityInputMethodSession session, int id) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AACS.sessionCreated"); - final long ident = Binder.clearCallingIdentity(); - try { - InputMethodManagerInternal.get().onSessionForAccessibilityCreated(id, session); - } finally { - Binder.restoreCallingIdentity(ident); - } - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } - @Override public void attachAccessibilityOverlayToDisplay( int interactionId, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index bb50a991d9c1..b5e8c849517b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -5378,19 +5378,37 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void requestImeLocked(AbstractAccessibilityServiceConnection connection) { + if (!(connection instanceof AccessibilityServiceConnection) + || (connection instanceof ProxyAccessibilityServiceConnection)) { + if (DEBUG) { + Slog.d(LOG_TAG, "The connection should be a real connection but was " + + connection); + } + return; + } + AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection; mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::createSessionForConnection, this, connection)); + AccessibilityManagerService::createSessionForConnection, this, realConnection)); mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::bindAndStartInputForConnection, this, connection)); + AccessibilityManagerService::bindAndStartInputForConnection, this, realConnection)); } @Override public void unbindImeLocked(AbstractAccessibilityServiceConnection connection) { + if (!(connection instanceof AccessibilityServiceConnection) + || (connection instanceof ProxyAccessibilityServiceConnection)) { + if (DEBUG) { + Slog.d(LOG_TAG, "The connection should be a real connection but was " + + connection); + } + return; + } + AccessibilityServiceConnection realConnection = (AccessibilityServiceConnection) connection; mMainHandler.sendMessage(obtainMessage( - AccessibilityManagerService::unbindInputForConnection, this, connection)); + AccessibilityManagerService::unbindInputForConnection, this, realConnection)); } - private void createSessionForConnection(AbstractAccessibilityServiceConnection connection) { + private void createSessionForConnection(AccessibilityServiceConnection connection) { synchronized (mLock) { if (mInputSessionRequested) { connection.createImeSessionLocked(); @@ -5398,7 +5416,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void bindAndStartInputForConnection(AbstractAccessibilityServiceConnection connection) { + private void bindAndStartInputForConnection(AccessibilityServiceConnection connection) { synchronized (mLock) { if (mInputBound) { connection.bindInputLocked(); @@ -5407,8 +5425,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void unbindInputForConnection(AbstractAccessibilityServiceConnection connection) { - InputMethodManagerInternal.get().unbindAccessibilityFromCurrentClient(connection.mId); + private void unbindInputForConnection(AccessibilityServiceConnection connection) { + InputMethodManagerInternal.get() + .unbindAccessibilityFromCurrentClient(connection.mId, connection.mUserId); synchronized (mLock) { connection.unbindInputLocked(); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 7a2a60263d38..40ca694dddd8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -18,6 +18,7 @@ package com.android.server.accessibility; import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN; import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS; +import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; @@ -27,6 +28,8 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.TouchInteractionController; +import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -38,12 +41,15 @@ import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings; import android.util.Slog; import android.view.Display; import android.view.MotionEvent; +import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -70,13 +76,38 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect Having the reference be null when being called is a very bad sign, but we check the condition. */ final WeakReference<AccessibilityUserState> mUserStateWeakReference; + @UserIdInt + final int mUserId; final Intent mIntent; final ActivityTaskManagerInternal mActivityTaskManagerService; private final Handler mMainHandler; - AccessibilityServiceConnection(AccessibilityUserState userState, Context context, - ComponentName componentName, + private static final class AccessibilityInputMethodSessionCallback + extends IAccessibilityInputMethodSessionCallback.Stub { + @UserIdInt + private final int mUserId; + + AccessibilityInputMethodSessionCallback(@UserIdInt int userId) { + mUserId = userId; + } + + @Override + public void sessionCreated(IAccessibilityInputMethodSession session, int id) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ASC.sessionCreated"); + final long ident = Binder.clearCallingIdentity(); + try { + InputMethodManagerInternal.get() + .onSessionForAccessibilityCreated(id, session, mUserId); + } finally { + Binder.restoreCallingIdentity(ident); + } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } + } + + AccessibilityServiceConnection(@Nullable AccessibilityUserState userState, + Context context, ComponentName componentName, AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler, Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport, AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, @@ -86,6 +117,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer, awm); mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState); + // the user ID doesn't matter when userState is null, because it is null only when this is a + // ProxyAccessibilityServiceConnection, for which it never creates an IME session and uses + // the user ID. + mUserId = userState == null ? UserHandle.USER_NULL : userState.mUserId; mIntent = new Intent().setComponent(mComponentName); mMainHandler = mainHandler; mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, @@ -557,6 +592,24 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return mRequestImeApis; } + @Override + protected void createImeSessionInternal() { + final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); + if (listener != null) { + try { + if (svcClientTracingEnabled()) { + logTraceSvcClient("createImeSession", ""); + } + AccessibilityInputMethodSessionCallback + callback = new AccessibilityInputMethodSessionCallback(mUserId); + listener.createImeSession(callback); + } catch (RemoteException re) { + Slog.e(LOG_TAG, + "Error requesting IME session from " + mService, re); + } + } + } + private void notifyMotionEventInternal(MotionEvent event) { final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); if (listener != null) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java index 6c6394faa09c..f0c44d64f5ec 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapper.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java @@ -35,15 +35,15 @@ import com.android.server.accessibility.AccessibilityTraceManager; /** * A wrapper of {@link IWindowMagnificationConnection}. */ -class WindowMagnificationConnectionWrapper { +class MagnificationConnectionWrapper { private static final boolean DBG = false; - private static final String TAG = "WindowMagnificationConnectionWrapper"; + private static final String TAG = "MagnificationConnectionWrapper"; private final @NonNull IWindowMagnificationConnection mConnection; private final @NonNull AccessibilityTraceManager mTrace; - WindowMagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection, + MagnificationConnectionWrapper(@NonNull IWindowMagnificationConnection connection, @NonNull AccessibilityTraceManager trace) { mConnection = connection; mTrace = trace; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index 816f22f8c7b0..3ea805bbb4a6 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -60,7 +60,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.concurrent.atomic.AtomicLongFieldUpdater; /** - * A class to manipulate window magnification through {@link WindowMagnificationConnectionWrapper} + * A class to manipulate window magnification through {@link MagnificationConnectionWrapper} * create by {@link #setConnection(IWindowMagnificationConnection)}. To set the connection with * SysUI, call {@code StatusBarManagerInternal#requestWindowMagnificationConnection(boolean)}. * The applied magnification scale is constrained by @@ -133,7 +133,7 @@ public class WindowMagnificationManager implements @VisibleForTesting @GuardedBy("mLock") @Nullable - WindowMagnificationConnectionWrapper mConnectionWrapper; + MagnificationConnectionWrapper mConnectionWrapper; @GuardedBy("mLock") private ConnectionCallback mConnectionCallback; @GuardedBy("mLock") @@ -245,7 +245,7 @@ public class WindowMagnificationManager implements } } if (connection != null) { - mConnectionWrapper = new WindowMagnificationConnectionWrapper(connection, mTrace); + mConnectionWrapper = new MagnificationConnectionWrapper(connection, mTrace); } if (mConnectionWrapper != null) { diff --git a/services/companion/Android.bp b/services/companion/Android.bp index fb8db21c3090..550e17be276d 100644 --- a/services/companion/Android.bp +++ b/services/companion/Android.bp @@ -21,7 +21,6 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [ ":services.companion-sources", - ":VirtualCamera-aidl-sources", ], libs: [ "app-compat-annotations", @@ -30,13 +29,6 @@ java_library_static { static_libs: [ "ukey2_jni", "virtualdevice_flags_lib", + "virtual_camera_service_aidl-java", ], } - -filegroup { - name: "VirtualCamera-aidl-sources", - srcs: [ - "java/com/android/server/companion/virtual/camera/*.aidl", - ], - path: "java", -} diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 2c608930b391..b9c269c91651 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -522,7 +522,8 @@ public class CompanionDeviceManagerService extends SystemService { private void notifyListeners( @UserIdInt int userId, @NonNull List<AssociationInfo> associations) { mListeners.broadcast((listener, callbackUserId) -> { - if ((int) callbackUserId == userId) { + int listenerUserId = (int) callbackUserId; + if (listenerUserId == userId || listenerUserId == UserHandle.USER_ALL) { try { listener.onAssociationsChanged(associations); } catch (RemoteException ignored) { @@ -660,6 +661,9 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId); + if (userId == UserHandle.USER_ALL) { + return List.copyOf(mAssociationStore.getAssociations()); + } return mAssociationStore.getAssociationsForUser(userId); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index ae8fddfd35ef..118943df1bf6 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,7 +51,7 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; -import android.companion.virtual.camera.IVirtualCamera; +import android.companion.virtual.camera.VirtualCameraConfig; import android.companion.virtual.flags.Flags; import android.companion.virtual.sensor.VirtualSensor; import android.companion.virtual.sensor.VirtualSensorEvent; @@ -277,7 +277,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub runningAppsChangedCallback, params, DisplayManagerGlobal.getInstance(), - Flags.virtualCamera() ? new VirtualCameraController(context) : null); + Flags.virtualCamera() ? new VirtualCameraController() : null); } @VisibleForTesting @@ -304,7 +304,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid()); mContext = context.createContextAsUser(ownerUserHandle, 0); mAssociationInfo = associationInfo; - mPersistentDeviceId = PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationInfo.getId(); + mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId()); mService = service; mPendingTrampolineCallback = pendingTrampolineCallback; mActivityListener = activityListener; @@ -380,6 +380,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mSensorController; } + static String createPersistentDeviceId(int associationId) { + return PERSISTENT_ID_PREFIX_CDM_ASSOCIATION + associationId; + } + /** * Returns the flags that should be added to any virtual displays created on this virtual * device. @@ -688,7 +692,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long ident = Binder.clearCallingIdentity(); try { mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId()); + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId())); } finally { Binder.restoreCallingIdentity(ident); } @@ -706,7 +711,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long ident = Binder.clearCallingIdentity(); try { mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId(), + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId()), config.getLanguageTag(), config.getLayoutType()); } finally { Binder.restoreCallingIdentity(ident); @@ -772,7 +778,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub try { mInputController.createNavigationTouchpad( config.getInputDeviceName(), config.getVendorId(), - config.getProductId(), deviceToken, config.getAssociatedDisplayId(), + config.getProductId(), deviceToken, + getTargetDisplayIdForInput(config.getAssociatedDisplayId()), touchpadHeight, touchpadWidth); } finally { Binder.restoreCallingIdentity(ident); @@ -950,13 +957,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) - public void registerVirtualCamera(@NonNull IVirtualCamera camera) { + public void registerVirtualCamera(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { super.registerVirtualCamera_enforcePermission(); + Objects.requireNonNull(cameraConfig); if (mVirtualCameraController == null) { - return; + throw new UnsupportedOperationException("Virtual camera controller is not available"); } - mVirtualCameraController.registerCamera(Objects.requireNonNull(camera)); + mVirtualCameraController.registerCamera(Objects.requireNonNull(cameraConfig)); + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void unregisterVirtualCamera(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { + super.unregisterVirtualCamera_enforcePermission(); + Objects.requireNonNull(cameraConfig); + if (mVirtualCameraController == null) { + throw new UnsupportedOperationException("Virtual camera controller is not available"); + } + mVirtualCameraController.unregisterCamera(Objects.requireNonNull(cameraConfig)); } @Override @@ -983,6 +1005,20 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + // For display mirroring, we want to dispatch all key events to the source (default) display, + // as the virtual display doesn't have any focused windows. Hence, call this for + // associating any input device to the source display if the input device emits any key events. + private int getTargetDisplayIdForInput(int displayId) { + if (!Flags.interactiveScreenMirror()) { + return displayId; + } + + DisplayManagerInternal displayManager = LocalServices.getService( + DisplayManagerInternal.class); + int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId); + return mirroredDisplayId == Display.INVALID_DISPLAY ? displayId : mirroredDisplayId; + } + @GuardedBy("mVirtualDeviceLock") private GenericWindowPolicyController createWindowPolicyControllerLocked( @NonNull Set<String> displayCategories) { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 92af68bc40a3..215970eedff1 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -27,6 +27,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.app.ActivityOptions; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceActivityListener; @@ -94,6 +95,11 @@ public class VirtualDeviceManagerService extends SystemService { private static final String VIRTUAL_DEVICE_NATIVE_SERVICE = "virtualdevice_native"; + private static final List<String> VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES = Arrays.asList( + AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, + AssociationRequest.DEVICE_PROFILE_APP_STREAMING, + AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING); + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; private final VirtualDeviceManagerNativeImpl mNativeImpl; @@ -105,6 +111,9 @@ public class VirtualDeviceManagerService extends SystemService { private static AtomicInteger sNextUniqueIndex = new AtomicInteger( Context.DEVICE_ID_DEFAULT + 1); + @GuardedBy("mVirtualDeviceManagerLock") + private List<AssociationInfo> mActiveAssociations = new ArrayList<>(); + private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener = new CompanionDeviceManager.OnAssociationsChangedListener() { @Override @@ -161,6 +170,7 @@ public class VirtualDeviceManagerService extends SystemService { }; @Override + @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES) public void onStart() { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); if (Flags.enableNativeVdm()) { @@ -172,6 +182,21 @@ public class VirtualDeviceManagerService extends SystemService { activityTaskManagerInternal.registerActivityStartInterceptor( VIRTUAL_DEVICE_SERVICE_ORDERED_ID, mActivityInterceptorCallback); + + if (Flags.persistentDeviceIdApi()) { + CompanionDeviceManager cdm = + getContext().getSystemService(CompanionDeviceManager.class); + if (cdm != null) { + synchronized (mVirtualDeviceManagerLock) { + mActiveAssociations = cdm.getAllAssociations(UserHandle.USER_ALL); + } + cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(), + this::onCdmAssociationsChanged, UserHandle.USER_ALL); + } else { + Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info " + + " will be available."); + } + } } void onCameraAccessBlocked(int appUid) { @@ -264,9 +289,11 @@ public class VirtualDeviceManagerService extends SystemService { try { getContext().sendBroadcastAsUser(i, UserHandle.ALL); - synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { - unregisterCdmAssociationListener(); + if (!Flags.persistentDeviceIdApi()) { + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + unregisterCdmAssociationListener(); + } } } } finally { @@ -316,6 +343,45 @@ public class VirtualDeviceManagerService extends SystemService { cdm.removeOnAssociationsChangedListener(mCdmAssociationListener); } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + void onCdmAssociationsChanged(List<AssociationInfo> associations) { + Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); + Set<String> removedPersistentDeviceIds = new HashSet<>(); + synchronized (mVirtualDeviceManagerLock) { + Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); + for (int i = 0; i < associations.size(); ++i) { + activeAssociationIds.add(associations.get(i).getId()); + } + + for (int i = 0; i < mActiveAssociations.size(); ++i) { + AssociationInfo associationInfo = mActiveAssociations.get(i); + if (!activeAssociationIds.contains(associationInfo.getId()) + && VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains( + associationInfo.getDeviceProfile())) { + removedPersistentDeviceIds.add( + VirtualDeviceImpl.createPersistentDeviceId(associationInfo.getId())); + } + } + + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); + if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) { + virtualDevicesToRemove.add(virtualDevice); + } + } + + mActiveAssociations = associations; + } + + for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { + virtualDevice.close(); + } + + if (!removedPersistentDeviceIds.isEmpty()) { + mLocalService.onPersistentDeviceIdsRemoved(removedPersistentDeviceIds); + } + } + private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() { synchronized (mVirtualDeviceManagerLock) { ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size()); @@ -393,7 +459,7 @@ public class VirtualDeviceManagerService extends SystemService { } synchronized (mVirtualDeviceManagerLock) { - if (mVirtualDevices.size() == 0) { + if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); try { registerCdmAssociationListener(); @@ -441,10 +507,8 @@ public class VirtualDeviceManagerService extends SystemService { + " is not the owner of the supplied VirtualDevice"); } - int displayId = virtualDeviceImpl.createVirtualDisplay(virtualDisplayConfig, callback, - packageName); - mLocalService.onVirtualDisplayCreated(displayId); - return displayId; + return virtualDeviceImpl.createVirtualDisplay( + virtualDisplayConfig, callback, packageName); } @Override // Binder call @@ -625,11 +689,12 @@ public class VirtualDeviceManagerService extends SystemService { private final class LocalService extends VirtualDeviceManagerInternal { @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList<VirtualDisplayListener> - mVirtualDisplayListeners = new ArrayList<>(); + private final ArrayList<AppsOnVirtualDeviceListener> mAppsOnVirtualDeviceListeners = + new ArrayList<>(); @GuardedBy("mVirtualDeviceManagerLock") - private final ArrayList<AppsOnVirtualDeviceListener> - mAppsOnVirtualDeviceListeners = new ArrayList<>(); + private final ArrayList<Consumer<String>> mPersistentDeviceIdRemovedListeners = + new ArrayList<>(); + @GuardedBy("mVirtualDeviceManagerLock") private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>(); @@ -665,35 +730,15 @@ public class VirtualDeviceManagerService extends SystemService { } @Override - public void onVirtualDisplayCreated(int displayId) { - final VirtualDisplayListener[] listeners; - synchronized (mVirtualDeviceManagerLock) { - listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); - } - mHandler.post(() -> { - for (VirtualDisplayListener listener : listeners) { - listener.onVirtualDisplayCreated(displayId); - } - }); - } - - @Override public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) { - final VirtualDisplayListener[] listeners; VirtualDeviceImpl virtualDeviceImpl; synchronized (mVirtualDeviceManagerLock) { - listeners = mVirtualDisplayListeners.toArray(new VirtualDisplayListener[0]); virtualDeviceImpl = mVirtualDevices.get( ((VirtualDeviceImpl) virtualDevice).getDeviceId()); } if (virtualDeviceImpl != null) { virtualDeviceImpl.onVirtualDisplayRemoved(displayId); } - mHandler.post(() -> { - for (VirtualDisplayListener listener : listeners) { - listener.onVirtualDisplayRemoved(displayId); - } - }); } @Override @@ -725,6 +770,22 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds) { + final List<Consumer<String>> persistentDeviceIdRemovedListeners; + synchronized (mVirtualDeviceManagerLock) { + persistentDeviceIdRemovedListeners = List.copyOf( + mPersistentDeviceIdRemovedListeners); + } + mHandler.post(() -> { + for (String persistentDeviceId : removedPersistentDeviceIds) { + for (Consumer<String> listener : persistentDeviceIdRemovedListeners) { + listener.accept(persistentDeviceId); + } + } + }); + } + + @Override public void onAuthenticationPrompt(int uid) { synchronized (mVirtualDeviceManagerLock) { for (int i = 0; i < mVirtualDevices.size(); i++) { @@ -791,6 +852,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override public @Nullable String getPersistentIdForDevice(int deviceId) { + if (deviceId == Context.DEVICE_ID_DEFAULT) { + return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + } + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { virtualDevice = mVirtualDevices.get(deviceId); @@ -799,34 +864,34 @@ public class VirtualDeviceManagerService extends SystemService { } @Override - public void registerVirtualDisplayListener( - @NonNull VirtualDisplayListener listener) { + public void registerAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { synchronized (mVirtualDeviceManagerLock) { - mVirtualDisplayListeners.add(listener); + mAppsOnVirtualDeviceListeners.add(listener); } } @Override - public void unregisterVirtualDisplayListener( - @NonNull VirtualDisplayListener listener) { + public void unregisterAppsOnVirtualDeviceListener( + @NonNull AppsOnVirtualDeviceListener listener) { synchronized (mVirtualDeviceManagerLock) { - mVirtualDisplayListeners.remove(listener); + mAppsOnVirtualDeviceListeners.remove(listener); } } @Override - public void registerAppsOnVirtualDeviceListener( - @NonNull AppsOnVirtualDeviceListener listener) { + public void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { synchronized (mVirtualDeviceManagerLock) { - mAppsOnVirtualDeviceListeners.add(listener); + mPersistentDeviceIdRemovedListeners.add(persistentDeviceIdRemovedListener); } } @Override - public void unregisterAppsOnVirtualDeviceListener( - @NonNull AppsOnVirtualDeviceListener listener) { + public void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener) { synchronized (mVirtualDeviceManagerLock) { - mAppsOnVirtualDeviceListeners.remove(listener); + mPersistentDeviceIdRemovedListeners.remove(persistentDeviceIdRemovedListener); } } } diff --git a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl b/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl deleted file mode 100644 index a4c1c4249697..000000000000 --- a/services/companion/java/com/android/server/companion/virtual/camera/IVirtualCameraService.aidl +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.virtual.camera; - -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; - -/** - * AIDL Interface to communicate with the VirtualCamera HAL - * @hide - */ -interface IVirtualCameraService { - - /** - * Registers a new camera with the virtual camera hal. - * @return true if the camera was successfully registered - */ - boolean registerCamera(in IVirtualCamera camera); - - /** - * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't - * be visible to the camera framework anymore. - */ - void unregisterCamera(in IVirtualCamera camera); -} diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java index 031d94962844..06be3f39dcd1 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java @@ -16,207 +16,127 @@ package com.android.server.companion.virtual.camera; +import static com.android.server.companion.virtual.camera.VirtualCameraConversionUtil.getServiceCameraConfiguration; + import android.annotation.NonNull; import android.annotation.Nullable; -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.VirtualCameraHalConfig; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.VirtualCameraConfiguration; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.UserHandle; -import android.util.Log; +import android.os.ServiceManager; +import android.util.ArraySet; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; -import java.util.HashMap; -import java.util.Map; +import java.util.Set; /** * Manages the registration and removal of virtual camera from the server side. * * <p>This classes delegate calls to the virtual camera service, so it is dependent on the service - * to be up and running + * to be up and running. */ -public class VirtualCameraController implements IBinder.DeathRecipient, ServiceConnection { - - private static class VirtualCameraInfo { - - private final IVirtualCamera mVirtualCamera; - private boolean mIsRegistered; - - VirtualCameraInfo(IVirtualCamera virtualCamera) { - mVirtualCamera = virtualCamera; - } - } +public final class VirtualCameraController implements IBinder.DeathRecipient { + private static final String VIRTUAL_CAMERA_SERVICE_NAME = "virtual_camera"; private static final String TAG = "VirtualCameraController"; - private static final String VIRTUAL_CAMERA_SERVICE_PACKAGE = "com.android.virtualcamera"; - private static final String VIRTUAL_CAMERA_SERVICE_CLASS = ".VirtualCameraService"; - private final Context mContext; - - @Nullable private IVirtualCameraService mVirtualCameraService = null; + @Nullable private IVirtualCameraService mVirtualCameraService; @GuardedBy("mCameras") - private final Map<IVirtualCamera, VirtualCameraInfo> mCameras = new HashMap<>(1); + private final Set<VirtualCameraConfig> mCameras = new ArraySet<>(); - public VirtualCameraController(Context context) { - mContext = context; + public VirtualCameraController() { connectVirtualCameraService(); } - private void connectVirtualCameraService() { - final long callingId = Binder.clearCallingIdentity(); - try { - Intent intent = new Intent(); - intent.setPackage(VIRTUAL_CAMERA_SERVICE_PACKAGE); - intent.setComponent( - ComponentName.createRelative( - VIRTUAL_CAMERA_SERVICE_PACKAGE, VIRTUAL_CAMERA_SERVICE_CLASS)); - mContext.startServiceAsUser(intent, UserHandle.SYSTEM); - if (!mContext.bindServiceAsUser( - intent, - this, - Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, - UserHandle.SYSTEM)) { - mContext.unbindService(this); - Log.w( - TAG, - "connectVirtualCameraService: Failed to connect to the virtual camera " - + "service"); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - private void forwardPendingRegistrations() { - IVirtualCameraService cameraService = mVirtualCameraService; - if (cameraService == null) { - return; - } - synchronized (mCameras) { - for (VirtualCameraInfo cameraInfo : mCameras.values()) { - if (cameraInfo.mIsRegistered) { - continue; - } - try { - cameraService.registerCamera(cameraInfo.mVirtualCamera); - cameraInfo.mIsRegistered = true; - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - } + @VisibleForTesting + VirtualCameraController(IVirtualCameraService virtualCameraService) { + mVirtualCameraService = virtualCameraService; } /** - * Remove the virtual camera with the provided name + * Register a new virtual camera with the given config. * - * @param camera The name of the camera to remove + * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ - public void unregisterCamera(@NonNull IVirtualCamera camera) { - IVirtualCameraService virtualCameraService = mVirtualCameraService; - if (virtualCameraService != null) { - try { - virtualCameraService.unregisterCamera(camera); + public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) { + // Try to connect to service if not connected already. + if (mVirtualCameraService == null) { + connectVirtualCameraService(); + } + // Throw exception if we are unable to connect to service. + if (mVirtualCameraService == null) { + throw new IllegalStateException("Virtual camera service is not connected."); + } + + try { + if (registerCameraWithService(cameraConfig)) { synchronized (mCameras) { - VirtualCameraInfo cameraInfo = mCameras.remove(camera); - cameraInfo.mIsRegistered = false; + mCameras.add(cameraConfig); } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + } else { + // TODO(b/310857519): Revisit this to find a better way of indicating failure. + throw new RuntimeException("Failed to register virtual camera."); } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } } /** - * Register a new virtual camera with the provided characteristics. + * Unregister the virtual camera with the given config. * - * @param camera The {@link IVirtualCamera} producing the image to communicate with the client. - * @throws IllegalArgumentException if the characteristics could not be parsed. + * @param cameraConfig The {@link VirtualCameraConfig} sent by the client. */ - public void registerCamera(@NonNull IVirtualCamera camera) { - IVirtualCameraService service = mVirtualCameraService; - VirtualCameraInfo virtualCameraInfo = new VirtualCameraInfo(camera); - synchronized (mCameras) { - mCameras.put(camera, virtualCameraInfo); - } - if (service != null) { - try { - if (service.registerCamera(camera)) { - virtualCameraInfo.mIsRegistered = true; - return; - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + public void unregisterCamera(@NonNull VirtualCameraConfig cameraConfig) { + try { + if (mVirtualCameraService == null) { + Slog.w(TAG, "Virtual camera service is not connected."); + } else { + mVirtualCameraService.unregisterCamera(cameraConfig.getCallback().asBinder()); } + synchronized (mCameras) { + mCameras.remove(cameraConfig); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } - - // Service was not available or registration failed, save the registration for later - connectVirtualCameraService(); } @Override public void binderDied() { - Log.d(TAG, "binderDied"); + Slog.d(TAG, "Virtual camera service died."); mVirtualCameraService = null; - } - - @Override - public void onBindingDied(ComponentName name) { - mVirtualCameraService = null; - Log.d(TAG, "onBindingDied() called with: name = [" + name + "]"); - } - - @Override - public void onNullBinding(ComponentName name) { - mVirtualCameraService = null; - Log.d(TAG, "onNullBinding() called with: name = [" + name + "]"); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - Log.d(TAG, "onServiceConnected: " + name.toString()); - mVirtualCameraService = IVirtualCameraService.Stub.asInterface(service); - try { - service.linkToDeath(this, 0); - } catch (RemoteException e) { - e.rethrowAsRuntimeException(); + synchronized (mCameras) { + mCameras.clear(); } - forwardPendingRegistrations(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - Log.d(TAG, "onServiceDisconnected() called with: name = [" + name + "]"); - mVirtualCameraService = null; } /** Release resources associated with this controller. */ public void close() { - if (mVirtualCameraService == null) { - return; - } synchronized (mCameras) { - mCameras.forEach( - (name, cameraInfo) -> { - try { - mVirtualCameraService.unregisterCamera(name); - } catch (RemoteException e) { - Log.w( - TAG, - "close(): Camera failed to be removed on camera service.", - e); - } - }); + if (mVirtualCameraService == null) { + Slog.w(TAG, "Virtual camera service is not connected."); + } else { + for (VirtualCameraConfig config : mCameras) { + try { + mVirtualCameraService.unregisterCamera(config.getCallback().asBinder()); + } catch (RemoteException e) { + Slog.w(TAG, "close(): Camera failed to be removed on camera " + + "service.", e); + } + } + } + mCameras.clear(); } - mContext.unbindService(this); + mVirtualCameraService = null; } /** Dumps information about this {@link VirtualCameraController} for debugging purposes. */ @@ -226,20 +146,34 @@ public class VirtualCameraController implements IBinder.DeathRecipient, ServiceC fout.printf("%sService:%s\n", indent, mVirtualCameraService); synchronized (mCameras) { fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size()); - for (VirtualCameraInfo info : mCameras.values()) { - VirtualCameraHalConfig config = null; - try { - config = info.mVirtualCamera.getHalConfig(); - } catch (RemoteException ex) { - Log.w(TAG, ex); - } - fout.printf( - "%s- %s isRegistered: %s, token: %s\n", - indent, - config == null ? "" : config.displayName, - info.mIsRegistered, - info.mVirtualCamera); + for (VirtualCameraConfig config : mCameras) { + fout.printf("%s token: %s\n", indent, config); + } + } + } + + private void connectVirtualCameraService() { + final long callingId = Binder.clearCallingIdentity(); + try { + IBinder virtualCameraBinder = + ServiceManager.waitForService(VIRTUAL_CAMERA_SERVICE_NAME); + if (virtualCameraBinder == null) { + Slog.e(TAG, "connectVirtualCameraService: Failed to connect to the virtual " + + "camera service"); + return; } + virtualCameraBinder.linkToDeath(this, 0); + mVirtualCameraService = IVirtualCameraService.Stub.asInterface(virtualCameraBinder); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } finally { + Binder.restoreCallingIdentity(callingId); } } + + private boolean registerCameraWithService(VirtualCameraConfig config) throws RemoteException { + VirtualCameraConfiguration serviceConfiguration = getServiceCameraConfiguration(config); + return mVirtualCameraService.registerCamera(config.getCallback().asBinder(), + serviceConfiguration); + } } diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java new file mode 100644 index 000000000000..202f68bdeb4a --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraConversionUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import android.annotation.NonNull; +import android.companion.virtual.camera.IVirtualCameraCallback; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.SupportedStreamConfiguration; +import android.companion.virtualcamera.VirtualCameraConfiguration; +import android.os.RemoteException; +import android.view.Surface; + +/** Utilities to convert the client side classes to the virtual camera service ones. */ +public final class VirtualCameraConversionUtil { + + /** + * Fetches the configuration of the provided virtual cameraConfig that was provided by its owner + * and convert it into the {@link IVirtualCameraService} types: {@link + * VirtualCameraConfiguration}. + * + * @param cameraConfig The cameraConfig sent by the client. + * @return The converted configuration to be sent to the {@link IVirtualCameraService}. + * @throws RemoteException If there was an issue fetching the configuration from the client. + */ + @NonNull + public static android.companion.virtualcamera.VirtualCameraConfiguration + getServiceCameraConfiguration(@NonNull VirtualCameraConfig cameraConfig) + throws RemoteException { + VirtualCameraConfiguration serviceConfiguration = new VirtualCameraConfiguration(); + + serviceConfiguration.supportedStreamConfigs = + cameraConfig.getStreamConfigs().stream() + .map(VirtualCameraConversionUtil::convertSupportedStreamConfiguration) + .toArray(SupportedStreamConfiguration[]::new); + + serviceConfiguration.virtualCameraCallback = convertCallback(cameraConfig.getCallback()); + return serviceConfiguration; + } + + @NonNull + private static android.companion.virtualcamera.IVirtualCameraCallback convertCallback( + @NonNull IVirtualCameraCallback camera) { + return new android.companion.virtualcamera.IVirtualCameraCallback.Stub() { + @Override + public void onStreamConfigured( + int streamId, Surface surface, int width, int height, int pixelFormat) + throws RemoteException { + VirtualCameraStreamConfig streamConfig = + createStreamConfig(width, height, pixelFormat); + camera.onStreamConfigured(streamId, surface, streamConfig); + } + + @Override + public void onStreamClosed(int streamId) throws RemoteException { + camera.onStreamClosed(streamId); + } + }; + } + + @NonNull + private static VirtualCameraStreamConfig createStreamConfig( + int width, int height, int pixelFormat) { + return new VirtualCameraStreamConfig(width, height, pixelFormat); + } + + @NonNull + private static SupportedStreamConfiguration convertSupportedStreamConfiguration( + VirtualCameraStreamConfig stream) { + SupportedStreamConfiguration supportedConfig = new SupportedStreamConfiguration(); + supportedConfig.height = stream.getHeight(); + supportedConfig.width = stream.getWidth(); + supportedConfig.pixelFormat = stream.getFormat(); + return supportedConfig; + } + + private VirtualCameraConversionUtil() { + } +} diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 065a447a7cd1..7e4cf4f35132 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -43,6 +43,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.function.pooled.PooledLambda; import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.pm.Installer.LegacyDexoptDisabledException; @@ -54,7 +55,6 @@ import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.snapshot.PackageDataSnapshot; @@ -1421,6 +1421,11 @@ public abstract class PackageManagerInternal { @UserIdInt int userId); /** + * Checks if package is stopped for a specific user. + */ + public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId); + + /** * Sends the PACKAGE_RESTARTED broadcast. */ public abstract void sendPackageRestartedBroadcast(@NonNull String packageName, diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index cd9c53bb92b9..80c4c58cea97 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -41,6 +41,18 @@ "file_patterns": ["StorageManagerService\\.java"] }, { + "name": "CtsScopedStorageBypassDatabaseOperationsTest", + "file_patterns": ["StorageManagerService\\.java"] + }, + { + "name": "CtsScopedStorageGeneralTest", + "file_patterns": ["StorageManagerService\\.java"] + }, + { + "name": "CtsScopedStorageRedactUriTest", + "file_patterns": ["StorageManagerService\\.java"] + }, + { "name": "FrameworksMockingServicesTests", "options": [ { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f92af6780883..b2a794865a48 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9367,6 +9367,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ void appendDropBoxProcessHeaders(ProcessRecord process, String processName, final VolatileDropboxEntryStates volatileStates, final StringBuilder sb) { + sb.append("SystemUptimeMs: ").append(SystemClock.uptimeMillis()).append("\n"); + // Watchdog thread ends up invoking this function (with // a null ProcessRecord) to add the stack file to dropbox. // Do not acquire a lock on this (am) in such cases, as it diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 9bba08ad1bfd..ddccce5963b3 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2230,8 +2230,10 @@ public class OomAdjuster { || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) { // This service has seen some activity within // recent memory, so we will keep its process ahead - // of the background processes. - if (adj > SERVICE_ADJ) { + // of the background processes. This does not apply + // to the SDK sandbox process since it should never + // be more important than its corresponding app. + if (!app.isSdkSandbox && adj > SERVICE_ADJ) { adj = SERVICE_ADJ; state.setAdjType("started-services"); if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index e5f763722bf1..7292ea6b19cb 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -148,6 +148,7 @@ import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; @@ -165,7 +166,6 @@ import com.android.server.pm.PackageManagerLocal; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.policy.AppOpsPolicy; import dalvik.annotation.optimization.NeverCompile; diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 283353ddc25d..c629b2b91603 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -25,6 +25,7 @@ import android.os.LocaleList; import android.util.ArraySet; import java.util.Set; +import java.util.function.Consumer; /** * Virtual device manager local service interface. @@ -32,29 +33,12 @@ import java.util.Set; */ public abstract class VirtualDeviceManagerInternal { - /** Interface to listen to the creation and destruction of virtual displays. */ - public interface VirtualDisplayListener { - /** Notifies that a virtual display was created. */ - void onVirtualDisplayCreated(int displayId); - - /** Notifies that a virtual display was removed. */ - void onVirtualDisplayRemoved(int displayId); - } - /** Interface to listen to the changes on the list of app UIDs running on any virtual device. */ public interface AppsOnVirtualDeviceListener { /** Notifies that running apps on any virtual device has changed */ void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids); } - /** Register a listener for the creation and destruction of virtual displays. */ - public abstract void registerVirtualDisplayListener( - @NonNull VirtualDisplayListener listener); - - /** Unregister a listener for the creation and destruction of virtual displays. */ - public abstract void unregisterVirtualDisplayListener( - @NonNull VirtualDisplayListener listener); - /** Register a listener for changes of running app UIDs on any virtual device. */ public abstract void registerAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener); @@ -63,6 +47,14 @@ public abstract class VirtualDeviceManagerInternal { public abstract void unregisterAppsOnVirtualDeviceListener( @NonNull AppsOnVirtualDeviceListener listener); + /** Register a listener for removal of persistent device IDs. */ + public abstract void registerPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + + /** Unregister a listener for the removal of persistent device IDs. */ + public abstract void unregisterPersistentDeviceIdRemovedListener( + @NonNull Consumer<String> persistentDeviceIdRemovedListener); + /** * Notifies that the set of apps running on virtual devices has changed. * This method only notifies the listeners when the union of running UIDs on all virtual devices @@ -76,6 +68,11 @@ public abstract class VirtualDeviceManagerInternal { public abstract void onAuthenticationPrompt(int uid); /** + * Notifies the given persistent device IDs have been removed. + */ + public abstract void onPersistentDeviceIdsRemoved(Set<String> removedPersistentDeviceIds); + + /** * Gets the owner uid for a deviceId. * * @param deviceId which device we're asking about @@ -104,13 +101,6 @@ public abstract class VirtualDeviceManagerInternal { public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid); /** - * Notifies that a virtual display is created. - * - * @param displayId The display id of the created virtual display. - */ - public abstract void onVirtualDisplayCreated(int displayId); - - /** * Notifies that a virtual display is removed. * * @param virtualDevice The virtual device where the virtual display located. diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 1b48e3ce5946..9f4b3d256b1e 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -1058,7 +1058,8 @@ public final class ContentService extends IContentService.Stub { final long identityToken = clearCallingIdentity(); try { - return getSyncManager().computeSyncable(account, userId, providerName, false); + return getSyncManager().computeSyncable(account, userId, providerName, false, + /*checkStoppedState=*/ false); } finally { restoreCallingIdentity(identityToken); } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index ac7d9c171247..575b30946fce 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -438,6 +438,23 @@ public class SyncManager { } }; + private final BroadcastReceiver mForceStoppedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG); + // For now, just log when packages were force-stopped and unstopped for debugging. + if (isLoggable) { + if (Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())) { + Log.d(TAG, "Package force-stopped: " + + intent.getData().getSchemeSpecificPart()); + } else if (Intent.ACTION_PACKAGE_UNSTOPPED.equals(intent.getAction())) { + Log.d(TAG, "Package unstopped: " + + intent.getData().getSchemeSpecificPart()); + } + } + } + }; + private final HandlerThread mThread; private final SyncHandler mSyncHandler; private final SyncManagerConstants mConstants; @@ -701,6 +718,12 @@ public class SyncManager { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + intentFilter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED); + intentFilter.addDataScheme("package"); + context.registerReceiver(mForceStoppedReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED); context.registerReceiver(mOtherIntentsReceiver, intentFilter); @@ -1108,7 +1131,7 @@ public class SyncManager { for (String authority : syncableAuthorities) { int isSyncable = computeSyncable(account.account, account.userId, authority, - !checkIfAccountReady); + !checkIfAccountReady, /*checkStoppedState=*/ true); if (isSyncable == AuthorityInfo.NOT_SYNCABLE) { continue; @@ -1228,7 +1251,7 @@ public class SyncManager { } public int computeSyncable(Account account, int userId, String authority, - boolean checkAccountAccess) { + boolean checkAccountAccess, boolean checkStoppedState) { final int status = getIsSyncable(account, userId, authority); if (status == AuthorityInfo.NOT_SYNCABLE) { return AuthorityInfo.NOT_SYNCABLE; @@ -1241,6 +1264,9 @@ public class SyncManager { } final int owningUid = syncAdapterInfo.uid; final String owningPackage = syncAdapterInfo.componentName.getPackageName(); + if (checkStoppedState && isPackageStopped(owningPackage, userId)) { + return AuthorityInfo.NOT_SYNCABLE; + } if (mAmi.isAppStartModeDisabled(owningUid, owningPackage)) { Slog.w(TAG, "Not scheduling job " + syncAdapterInfo.uid + ":" + syncAdapterInfo.componentName @@ -1256,6 +1282,17 @@ public class SyncManager { return status; } + /** + * Returns whether the package is in a stopped state or not. + * Always returns {@code false} if the {@code android.content.pm.stay_stopped} flag is not set. + */ + private boolean isPackageStopped(String packageName, int userId) { + if (android.content.pm.Flags.stayStopped()) { + return mPackageManagerInternal.isPackageStopped(packageName, userId); + } + return false; + } + private boolean canAccessAccount(Account account, String packageName, int uid) { if (mAccountManager.hasAccountAccess(account, packageName, UserHandle.getUserHandleForUid(uid))) { @@ -3496,6 +3533,9 @@ public class SyncManager { for (SyncOperation op: ops) { if (op.isPeriodic && op.target.matchesSpec(target) && op.areExtrasEqual(extras, /*includeSyncSettings=*/ true)) { + if (isPackageStopped(op.owningPackage, target.userId)) { + continue; // skip stopped package + } maybeUpdateSyncPeriodH(op, pollFrequencyMillis, flexMillis); return; } @@ -3627,7 +3667,8 @@ public class SyncManager { } } // Drop this sync request if it isn't syncable. - state = computeSyncable(target.account, target.userId, target.provider, true); + state = computeSyncable(target.account, target.userId, target.provider, true, + /*checkStoppedState=*/ true); if (state == AuthorityInfo.SYNCABLE_NO_ACCOUNT_ACCESS) { if (isLoggable) { Slog.v(TAG, " Dropping sync operation: " diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a0beedb1aa64..b99de5cc0c7b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -71,7 +71,7 @@ import com.android.server.display.config.RefreshRateThrottlingPoint; import com.android.server.display.config.RefreshRateZone; import com.android.server.display.config.SdrHdrRatioMap; import com.android.server.display.config.SdrHdrRatioPoint; -import com.android.server.display.config.SensorDetails; +import com.android.server.display.config.SensorData; import com.android.server.display.config.ThermalStatus; import com.android.server.display.config.ThermalThrottling; import com.android.server.display.config.ThresholdPoint; @@ -349,6 +349,20 @@ import javax.xml.datatype.DatatypeConfigurationException; * <proxSensor> * <type>android.sensor.proximity</type> * <name>1234 Proximity Sensor</name> + * <refreshRate> + * <minimum>60</minimum> + * <maximum>60</maximum> + * </refreshRate> + * <supportedModes> + * <point> + * <first>60</first> // refreshRate + * <second>60</second> //vsyncRate + * </point> + * <point> + * <first>120</first> // refreshRate + * <second>120</second> //vsyncRate + * </point> + * </supportedModes> * </proxSensor> * * <ambientLightHorizonLong>10001</ambientLightHorizonLong> @@ -581,15 +595,15 @@ public class DisplayDeviceConfig { private final Context mContext; // The details of the ambient light sensor associated with this display. - private final SensorData mAmbientLightSensor = new SensorData(); + private SensorData mAmbientLightSensor; // The details of the doze brightness sensor associated with this display. - private final SensorData mScreenOffBrightnessSensor = new SensorData(); + private SensorData mScreenOffBrightnessSensor; // The details of the proximity sensor associated with this display. // Is null when no sensor should be used for that display @Nullable - private SensorData mProximitySensor = new SensorData(); + private SensorData mProximitySensor; private final List<RefreshRateLimitation> mRefreshRateLimitations = new ArrayList<>(2 /*initialCapacity*/); @@ -1913,9 +1927,10 @@ public class DisplayDeviceConfig { loadLuxThrottling(config); loadQuirks(config); loadBrightnessRamps(config); - loadAmbientLightSensorFromDdc(config); - loadScreenOffBrightnessSensorFromDdc(config); - loadProxSensorFromDdc(config); + mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(config, + mContext.getResources()); + mScreenOffBrightnessSensor = SensorData.loadScreenOffBrightnessSensorConfig(config); + mProximitySensor = SensorData.loadProxSensorConfig(config); loadAmbientHorizonFromDdc(config); loadBrightnessChangeThresholds(config); loadAutoBrightnessConfigValues(config); @@ -1940,9 +1955,9 @@ public class DisplayDeviceConfig { loadBrightnessConstraintsFromConfigXml(); loadBrightnessMapFromConfigXml(); loadBrightnessRampsFromConfigXml(); - loadAmbientLightSensorFromConfigXml(); + mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources()); + mProximitySensor = SensorData.loadSensorUnspecifiedConfig(); loadBrightnessChangeThresholdsFromXml(); - setProxSensorUnspecified(); loadAutoBrightnessConfigsFromConfigXml(); loadAutoBrightnessAvailableFromConfigXml(); loadRefreshRateSetting(null); @@ -1966,8 +1981,8 @@ public class DisplayDeviceConfig { mBrightnessRampDecreaseMaxIdleMillis = 0; mBrightnessRampIncreaseMaxIdleMillis = 0; setSimpleMappingStrategyValues(); - loadAmbientLightSensorFromConfigXml(); - setProxSensorUnspecified(); + mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources()); + mProximitySensor = SensorData.loadSensorUnspecifiedConfig(); loadAutoBrightnessAvailableFromConfigXml(); } @@ -2919,64 +2934,10 @@ public class DisplayDeviceConfig { mBrightnessRampSlowDecrease = mBrightnessRampSlowIncrease; } - private void loadAmbientLightSensorFromConfigXml() { - mAmbientLightSensor.name = ""; - mAmbientLightSensor.type = mContext.getResources().getString( - com.android.internal.R.string.config_displayLightSensorType); - } - private void loadAutoBrightnessConfigsFromConfigXml() { loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/); } - private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) { - final SensorDetails sensorDetails = config.getLightSensor(); - if (sensorDetails != null) { - loadSensorData(sensorDetails, mAmbientLightSensor); - } else { - loadAmbientLightSensorFromConfigXml(); - } - } - - private void setProxSensorUnspecified() { - mProximitySensor = new SensorData(); - } - - private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) { - final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor(); - if (sensorDetails != null) { - loadSensorData(sensorDetails, mScreenOffBrightnessSensor); - } - } - - private void loadProxSensorFromDdc(DisplayConfiguration config) { - SensorDetails sensorDetails = config.getProxSensor(); - if (sensorDetails != null) { - String name = sensorDetails.getName(); - String type = sensorDetails.getType(); - if ("".equals(name) && "".equals(type)) { - // <proxSensor> with empty values to the config means no sensor should be used - mProximitySensor = null; - } else { - mProximitySensor = new SensorData(); - loadSensorData(sensorDetails, mProximitySensor); - } - } else { - setProxSensorUnspecified(); - } - } - - private void loadSensorData(@NonNull SensorDetails sensorDetails, - @NonNull SensorData sensorData) { - sensorData.name = sensorDetails.getName(); - sensorData.type = sensorDetails.getType(); - final RefreshRateRange rr = sensorDetails.getRefreshRate(); - if (rr != null) { - sensorData.minRefreshRate = rr.getMinimum().floatValue(); - sensorData.maxRefreshRate = rr.getMaximum().floatValue(); - } - } - private void loadBrightnessChangeThresholdsFromXml() { loadBrightnessChangeThresholds(/* config= */ null); } @@ -3390,37 +3351,6 @@ public class DisplayDeviceConfig { } /** - * Uniquely identifies a Sensor, with the combination of Type and Name. - */ - public static class SensorData { - public String type; - public String name; - public float minRefreshRate = 0.0f; - public float maxRefreshRate = Float.POSITIVE_INFINITY; - - @Override - public String toString() { - return "Sensor{" - + "type: " + type - + ", name: " + name - + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]" - + "} "; - } - - /** - * @return True if the sensor matches both the specified name and type, or one if only one - * is specified (not-empty). Always returns false if both parameters are null or empty. - */ - public boolean matches(String sensorName, String sensorType) { - final boolean isNameSpecified = !TextUtils.isEmpty(sensorName); - final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType); - return (isNameSpecified || isTypeSpecified) - && (!isNameSpecified || sensorName.equals(name)) - && (!isTypeSpecified || sensorType.equals(type)); - } - } - - /** * Container for high brightness mode configuration data. */ static class HighBrightnessModeData { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index eae153cb8aeb..8046dbf3d4fd 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -158,7 +158,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; -import com.android.server.display.DisplayDeviceConfig.SensorData; +import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d8ac52eb8c79..5761c31b29bf 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1540,12 +1540,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call performScreenOffTransition = true; break; case DisplayPowerRequest.POLICY_DOZE: - if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { + if (mDozeStateOverride != Display.STATE_UNKNOWN) { + state = mDozeStateOverride; + } else if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { state = mPowerRequest.dozeScreenState; } else { state = Display.STATE_DOZE; } - state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride; if (!mAllowAutoBrightnessWhileDozingConfig) { brightnessState = mPowerRequest.dozeScreenBrightness; mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE); diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java new file mode 100644 index 000000000000..3bb35bf7c49f --- /dev/null +++ b/services/core/java/com/android/server/display/config/SensorData.java @@ -0,0 +1,184 @@ +/* + * 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.display.config; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources; +import android.text.TextUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Uniquely identifies a Sensor, with the combination of Type and Name. + */ +public class SensorData { + + @Nullable + public final String type; + @Nullable + public final String name; + public final float minRefreshRate; + public final float maxRefreshRate; + public final List<SupportedMode> supportedModes; + + @VisibleForTesting + public SensorData() { + this(/* type= */ null, /* name= */ null); + } + + @VisibleForTesting + public SensorData(String type, String name) { + this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY); + } + + @VisibleForTesting + public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) { + this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of()); + } + + @VisibleForTesting + public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate, + List<SupportedMode> supportedModes) { + this.type = type; + this.name = name; + this.minRefreshRate = minRefreshRate; + this.maxRefreshRate = maxRefreshRate; + this.supportedModes = Collections.unmodifiableList(supportedModes); + } + + /** + * @return True if the sensor matches both the specified name and type, or one if only one + * is specified (not-empty). Always returns false if both parameters are null or empty. + */ + public boolean matches(String sensorName, String sensorType) { + final boolean isNameSpecified = !TextUtils.isEmpty(sensorName); + final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType); + return (isNameSpecified || isTypeSpecified) + && (!isNameSpecified || sensorName.equals(name)) + && (!isTypeSpecified || sensorType.equals(type)); + } + + @Override + public String toString() { + return "SensorData{" + + "type= " + type + + ", name= " + name + + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]" + + ", supportedModes=" + supportedModes + + '}'; + } + + /** + * Loads ambient light sensor data from DisplayConfiguration and if missing from resources xml + */ + public static SensorData loadAmbientLightSensorConfig(DisplayConfiguration config, + Resources resources) { + SensorDetails sensorDetails = config.getLightSensor(); + if (sensorDetails != null) { + return loadSensorData(sensorDetails); + } else { + return loadAmbientLightSensorConfig(resources); + } + } + + /** + * Loads ambient light sensor data from resources xml + */ + public static SensorData loadAmbientLightSensorConfig(Resources resources) { + return new SensorData( + resources.getString(com.android.internal.R.string.config_displayLightSensorType), + /* name= */ ""); + } + + /** + * Loads screen off brightness sensor data from DisplayConfiguration + */ + public static SensorData loadScreenOffBrightnessSensorConfig(DisplayConfiguration config) { + SensorDetails sensorDetails = config.getScreenOffBrightnessSensor(); + if (sensorDetails != null) { + return loadSensorData(sensorDetails); + } else { + return new SensorData(); + } + } + + /** + * Loads proximity sensor data from DisplayConfiguration + */ + @Nullable + public static SensorData loadProxSensorConfig(DisplayConfiguration config) { + SensorDetails sensorDetails = config.getProxSensor(); + if (sensorDetails != null) { + String name = sensorDetails.getName(); + String type = sensorDetails.getType(); + if ("".equals(name) && "".equals(type)) { + // <proxSensor> with empty values to the config means no sensor should be used. + // See also {@link com.android.server.display.utils.SensorUtils} + return null; + } else { + return loadSensorData(sensorDetails); + } + } else { + return new SensorData(); + } + } + + /** + * Loads sensor unspecified config, this means system should use default sensor. + * See also {@link com.android.server.display.utils.SensorUtils} + */ + @NonNull + public static SensorData loadSensorUnspecifiedConfig() { + return new SensorData(); + } + + private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) { + float minRefreshRate = 0f; + float maxRefreshRate = Float.POSITIVE_INFINITY; + RefreshRateRange rr = sensorDetails.getRefreshRate(); + if (rr != null) { + minRefreshRate = rr.getMinimum().floatValue(); + maxRefreshRate = rr.getMaximum().floatValue(); + } + ArrayList<SupportedMode> supportedModes = new ArrayList<>(); + NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes(); + if (configSupportedModes != null) { + for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) { + supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(), + supportedMode.getSecond().floatValue())); + } + } + + return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate, + maxRefreshRate, supportedModes); + } + + public static class SupportedMode { + public final float refreshRate; + public final float vsyncRate; + + public SupportedMode(float refreshRate, float vsyncRate) { + this.refreshRate = refreshRate; + this.vsyncRate = vsyncRate; + } + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index d023913c9694..fb6c9e3cab9d 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -724,8 +724,9 @@ public class DisplayModeDirector { || mode.getPhysicalHeight() > maxAllowedHeight || mode.getPhysicalWidth() < outSummary.minWidth || mode.getPhysicalHeight() < outSummary.minHeight - || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate - || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) { + || mode.getRefreshRate() < (outSummary.minPhysicalRefreshRate - FLOAT_TOLERANCE) + || mode.getRefreshRate() > (outSummary.maxPhysicalRefreshRate + FLOAT_TOLERANCE) + ) { continue; } diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java index 5d6e65071479..5f289349751f 100644 --- a/services/core/java/com/android/server/display/state/DisplayStateController.java +++ b/services/core/java/com/android/server/display/state/DisplayStateController.java @@ -61,12 +61,13 @@ public class DisplayStateController { mPerformScreenOffTransition = true; break; case DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE: - if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { + if (mDozeStateOverride != Display.STATE_UNKNOWN) { + state = mDozeStateOverride; + } else if (displayPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) { state = displayPowerRequest.dozeScreenState; } else { state = Display.STATE_DOZE; } - state = mDozeStateOverride == Display.STATE_UNKNOWN ? state : mDozeStateOverride; break; case DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM: case DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT: diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java index 56321cd01e20..8b9fe1083187 100644 --- a/services/core/java/com/android/server/display/utils/SensorUtils.java +++ b/services/core/java/com/android/server/display/utils/SensorUtils.java @@ -21,7 +21,7 @@ import android.hardware.Sensor; import android.hardware.SensorManager; import android.text.TextUtils; -import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.config.SensorData; import java.util.List; @@ -36,7 +36,7 @@ public class SensorUtils { */ @Nullable public static Sensor findSensor(@Nullable SensorManager sensorManager, - @Nullable DisplayDeviceConfig.SensorData sensorData, int fallbackType) { + @Nullable SensorData sensorData, int fallbackType) { if (sensorData == null) { return null; } else { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d3eedd7e089a..168715713f8d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -191,9 +191,16 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchDeviceDiscovery(); startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { - mService.sendCecCommand( - HdmiCecMessageBuilder.buildRequestActiveSource( - getDeviceInfo().getLogicalAddress())); + addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { + @Override + public void onComplete(int result) { + if (result != HdmiControlManager.RESULT_SUCCESS) { + mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource( + getDeviceInfo().getLogicalAddress(), + getDeviceInfo().getPhysicalAddress())); + } + } + })); } } @@ -1325,6 +1332,8 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { removeAction(TimerRecordingAction.class); removeAction(NewDeviceAction.class); removeAction(AbsoluteVolumeAudioStatusAction.class); + // Remove pending actions. + removeAction(RequestActiveSourceAction.class); // Keep SAM enabled if eARC is enabled, unless we're going to Standby. if (initiatedByCec || !mService.isEarcEnabled()){ diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java new file mode 100644 index 000000000000..017c86d4b363 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java @@ -0,0 +1,66 @@ +/* + * 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.hdmi; + +import android.hardware.hdmi.HdmiControlManager; +import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; + +/** + * Feature action that sends <Request Active Source> message and waits for <Active Source>. + */ +public class RequestActiveSourceAction extends HdmiCecFeatureAction { + private static final String TAG = "RequestActiveSourceAction"; + + // State to wait for the <Active Source> message. + private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1; + + RequestActiveSourceAction(HdmiCecLocalDevice source, IHdmiControlCallback callback) { + super(source, callback); + } + + @Override + boolean start() { + Slog.v(TAG, "RequestActiveSourceAction started."); + + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + + mState = STATE_WAIT_FOR_ACTIVE_SOURCE; + addTimer(mState, HdmiConfig.TIMEOUT_MS); + return true; + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + // The action finishes successfully if the <Active Source> message is received. + // {@link HdmiCecLocalDevice#onMessage} handles this message, so false is returned. + if (cmd.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_SUCCESS); + } + return false; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) { + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 3e46ee29346e..a46d7197100f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -171,17 +171,20 @@ public abstract class InputMethodManagerInternal { * * @param accessibilityConnectionId the connection id of the accessibility service * @param session the session passed back from the accessibility service + * @param userId the user ID to be queried */ public abstract void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IAccessibilityInputMethodSession session); + IAccessibilityInputMethodSession session, @UserIdInt int userId); /** * Unbind the accessibility service with the specified accessibilityConnectionId from current * client. * * @param accessibilityConnectionId the connection id of the accessibility service + * @param userId the user ID to be queried */ - public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId); + public abstract void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, + @UserIdInt int userId); /** * Switch the keyboard layout in response to a keyboard shortcut. @@ -266,11 +269,12 @@ public abstract class InputMethodManagerInternal { @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IAccessibilityInputMethodSession session) { + IAccessibilityInputMethodSession session, @UserIdInt int userId) { } @Override - public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) { + public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, + @UserIdInt int userId) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d656f204ebaa..6cc069377bf2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5716,8 +5716,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void onSessionForAccessibilityCreated(int accessibilityConnectionId, - IAccessibilityInputMethodSession session) { + IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { + // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); mCurClient.mAccessibilitySessions.put(accessibilityConnectionId, @@ -5744,8 +5745,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId) { + public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, + @UserIdInt int userId) { synchronized (ImfLock.class) { + // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { if (DEBUG) { Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client=" diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index 550ad5d610da..392b86e2df1f 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -74,7 +74,6 @@ import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; import com.android.internal.util.HexDump; import com.android.modules.utils.build.SdkLevel; -import com.android.net.flags.Flags; import com.android.net.module.util.NetdUtils; import com.android.net.module.util.PermissionUtils; import com.android.server.FgThread; @@ -1062,7 +1061,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setDataSaverModeEnabled"); try { - if (Flags.setDataSaverViaCm()) { + if (SdkLevel.isAtLeastV()) { // setDataSaverEnabled throws if it fails to set data saver. mContext.getSystemService(ConnectivityManager.class) .setDataSaverEnabled(enable); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 707e99019df3..bae06347d8a2 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -360,6 +360,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; @@ -5245,13 +5246,23 @@ public class NotificationManagerService extends SystemService { } } + // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. @Override public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException { - enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules"); + enforcePolicyAccess(Binder.getCallingUid(), "getZenRules"); return mZenModeHelper.getZenRules(); } @Override + public Map<String, AutomaticZenRule> getAutomaticZenRules() { + if (!android.app.Flags.modesApi()) { + throw new IllegalStateException("getAutomaticZenRules called with flag off!"); + } + enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules"); + return mZenModeHelper.getAutomaticZenRules(); + } + + @Override public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException { Objects.requireNonNull(id, "Id is null"); enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule"); @@ -7031,9 +7042,8 @@ public class NotificationManagerService extends SystemService { channelId = (new Notification.TvExtender(notification)).getChannelId(); } String shortcutId = n.getShortcutId(); - final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( - pkg, notificationUid, channelId, shortcutId, - true /* parent ok */, false /* includeDeleted */); + final NotificationChannel channel = getNotificationChannelRestoreDeleted(pkg, + callingUid, notificationUid, channelId, shortcutId); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg @@ -7151,6 +7161,35 @@ public class NotificationManagerService extends SystemService { return true; } + /** + * Returns a channel, if exists, and restores deleted conversation channels. + */ + @Nullable + private NotificationChannel getNotificationChannelRestoreDeleted(String pkg, + int callingUid, int notificationUid, String channelId, String conversationId) { + // Restore a deleted conversation channel, if exists. Otherwise use the parent channel. + NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( + pkg, notificationUid, channelId, conversationId, + true /* parent ok */, !TextUtils.isEmpty(conversationId) /* includeDeleted */); + // Restore deleted conversation channel + if (channel != null && channel.isDeleted()) { + if (Objects.equals(conversationId, channel.getConversationId())) { + boolean needsPolicyFileChange = mPreferencesHelper.createNotificationChannel( + pkg, notificationUid, channel, true /* fromTargetApp */, + mConditionProviders.isPackageOrComponentAllowed(pkg, + UserHandle.getUserId(notificationUid)), callingUid, true); + // Update policy file if the conversation channel was restored + if (needsPolicyFileChange) { + handleSavePolicyFile(); + } + } else { + // Do not restore parent channel + channel = null; + } + } + return channel; + } + private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) { checkCallerIsSystem(); Preconditions.checkStringNotEmpty(pkg); @@ -11449,17 +11488,16 @@ public class NotificationManagerService extends SystemService { } } } - } - // clean up anything in the disallowed pkgs list - for (int i = 0; i < pkgList.length; i++) { - String pkg = pkgList[i]; - for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) { - NotificationListenerFilter nlf = - mRequestedNotificationListeners.valueAt(j); - - VersionedPackage ai = new VersionedPackage(pkg, uidList[i]); - nlf.removePackage(ai); + // Clean up removed package from the disallowed packages list + for (int i = 0; i < pkgList.length; i++) { + String pkg = pkgList[i]; + for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) { + NotificationListenerFilter nlf = + mRequestedNotificationListeners.valueAt(j); + VersionedPackage ai = new VersionedPackage(pkg, uidList[i]); + nlf.removePackage(ai); + } } } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index c637df2ec99b..a1704c6619ad 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -106,7 +106,9 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -334,6 +336,7 @@ public class ZenModeHelper { return mZenMode; } + // TODO: b/310620812 - Make private (or inline) when MODES_API is inlined. public List<ZenRule> getZenRules() { List<ZenRule> rules = new ArrayList<>(); synchronized (mConfigLock) { @@ -347,6 +350,20 @@ public class ZenModeHelper { return rules; } + /** + * Get the list of {@link AutomaticZenRule} instances that the calling package can manage + * (which means the owned rules for a regular app, and every rule for system callers) together + * with their ids. + */ + Map<String, AutomaticZenRule> getAutomaticZenRules() { + List<ZenRule> ruleList = getZenRules(); + HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size()); + for (ZenRule rule : ruleList) { + rules.put(rule.id, zenRuleToAutomaticZenRule(rule)); + } + return rules; + } + public AutomaticZenRule getAutomaticZenRule(String id) { ZenRule rule; synchronized (mConfigLock) { diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 21284a05ddad..659c36c56047 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -45,11 +45,11 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.utils.TimingsTraceAndSlog; import com.google.android.collect.Lists; diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 21610c9510ac..bdcec3a33221 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -58,6 +58,9 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; @@ -68,9 +71,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java index d38b83fa6758..f3f64c5010ee 100644 --- a/services/core/java/com/android/server/pm/AppsFilterUtils.java +++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java @@ -27,15 +27,15 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.utils.WatchedArraySet; import com.android.server.utils.WatchedSparseSetArray; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 26177037e1b4..5e76ae5cc2c3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -123,6 +123,12 @@ import android.util.Xml; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -141,12 +147,6 @@ import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 8bf903ae0b13..f45571ab28e4 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -578,6 +578,12 @@ final class DeletePackageHelper { ? null : ps.getUserStateOrDefault(nextUserId).getArchiveState(); + // Preserve firstInstallTime in case of DELETE_KEEP_DATA + // For full uninstalls, reset firstInstallTime to 0 as if it has never been installed + final long firstInstallTime = (flags & DELETE_KEEP_DATA) == 0 + ? 0 + : ps.getUserStateOrDefault(nextUserId).getFirstInstallTimeMillis(); + ps.setUserState(nextUserId, ps.getCeDataInode(nextUserId), ps.getDeDataInode(nextUserId), @@ -597,7 +603,7 @@ final class DeletePackageHelper { PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashScreenTheme*/, - 0 /*firstInstallTime*/, + firstInstallTime, PackageManager.USER_MIN_ASPECT_RATIO_UNSET, archiveState); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index f8d27f1bad1c..d46d55977d2f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -154,6 +154,11 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.F2fsUtils; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -177,11 +182,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedLibraryWrapper; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.rollback.RollbackManagerInternal; import com.android.server.security.FileIntegrityService; diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index e7499680b9a2..b281808e89b6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -47,13 +47,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.dex.DexManager; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import java.io.IOException; @@ -758,6 +758,11 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal { return snapshot().isPackageQuarantinedForUser(packageName, userId); } + @Override + public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) { + return snapshot().isPackageStoppedForUser(packageName, userId); + } + @NonNull @Override @Deprecated diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 434c00afa8b2..7d3d85d33dcb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -181,6 +181,8 @@ import com.android.internal.app.ResolverActivity; import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.om.OverlayConfig; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.telephony.CarrierAppUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -233,8 +235,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.mutate.PackageStateMutator; import com.android.server.pm.pkg.mutate.PackageStateWrite; import com.android.server.pm.pkg.mutate.PackageUserStateWrite; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index bcb7bdebf9bf..cd3416348153 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -92,6 +92,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.content.InstallLocationUtils; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.HexDump; @@ -104,7 +105,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.resolution.ComponentResolverApi; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/PackageProperty.java b/services/core/java/com/android/server/pm/PackageProperty.java index 241f14390d5e..651bc5fac06a 100644 --- a/services/core/java/com/android/server/pm/PackageProperty.java +++ b/services/core/java/com/android/server/pm/PackageProperty.java @@ -31,8 +31,8 @@ import android.os.Binder; import android.os.UserHandle; import android.util.ArrayMap; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedComponent; import java.util.ArrayList; import java.util.Iterator; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index b055a3ffd688..a8196f3f4985 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -44,6 +44,7 @@ import android.util.Slog; import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.ArrayUtils; import com.android.server.pm.Installer.LegacyDexoptDisabledException; import com.android.server.pm.parsing.PackageCacher; @@ -52,7 +53,6 @@ import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import java.io.File; import java.util.Collections; diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 7ea9e3f529d0..22ee963cf9a2 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -73,6 +73,11 @@ import android.util.jar.StrictJarFile; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.parsing.PackageInfoUtils; @@ -82,11 +87,6 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedArraySet; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6338965e4a80..7c969ef11666 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -89,6 +89,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -113,10 +117,6 @@ import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.SuspendParams; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.resolution.ComponentResolver; import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index 9376259c08b1..dddc6b0fbb7a 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -25,13 +25,13 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.ArrayUtils; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; diff --git a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java index adac68b25749..215646778778 100644 --- a/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java +++ b/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java @@ -29,9 +29,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import org.xmlpull.v1.XmlPullParser; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 81a570f0e7a5..4e14c908b01b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1388,10 +1388,32 @@ public class UserManagerService extends IUserManager.Stub { final long identity = Binder.clearCallingIdentity(); try { + // QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL is only allowed for managed-profiles + if (dontAskCredential) { + UserInfo userInfo; + synchronized (mUsersLock) { + userInfo = getUserInfo(userId); + } + if (!userInfo.isManagedProfile()) { + throw new IllegalArgumentException("Invalid flags: " + flags + + ". Can't skip credential check for the user"); + } + } if (enableQuietMode) { setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage); return true; } + if (android.os.Flags.allowPrivateProfile()) { + final UserProperties userProperties = getUserPropertiesInternal(userId); + if (userProperties != null + && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) { + if (onlyIfCredentialNotRequired) { + return false; + } + showConfirmCredentialToDisableQuietMode(userId, target); + return false; + } + } final boolean hasUnifiedChallenge = mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId); if (hasUnifiedChallenge) { diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 29e0c35d88bd..7da76c18216e 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -193,6 +193,7 @@ public final class UserTypeFactory { .setStartWithParent(true) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) + .setAuthAlwaysRequiredToDisableQuietMode(false) .setCredentialShareableWithParent(true)); } @@ -292,7 +293,8 @@ public final class UserTypeFactory { .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) .setDefaultUserProperties(new UserProperties.Builder() .setStartWithParent(true) - .setCredentialShareableWithParent(false) + .setCredentialShareableWithParent(true) + .setAuthAlwaysRequiredToDisableQuietMode(true) .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 38cde3e3f7d7..91a70a6b4bdf 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -52,6 +52,17 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageArchiver; @@ -65,17 +76,6 @@ import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentParseUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java index 97d526d1c44e..8916efd7aa5a 100644 --- a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java +++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java @@ -20,8 +20,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedComponent; /** * For exposing internal fields to the rest of the server, enforcing that any overridden state from diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index e2acc17e94c4..0eb2bbde5886 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -29,16 +29,16 @@ import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; import com.android.internal.content.NativeLibraryHelper; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; import com.android.server.pm.PackageManagerException; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 75dd67d97620..370d239ad329 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -50,6 +50,19 @@ import android.util.SparseIntArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; @@ -61,27 +74,15 @@ import com.android.server.pm.pkg.AndroidPackageSplit; import com.android.server.pm.pkg.AndroidPackageSplitImpl; import com.android.server.pm.pkg.SELinuxUtil; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionImpl; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; +import com.android.server.pm.pkg.component.ParsedProcessImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageHidden; @@ -3305,7 +3306,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.instrumentations = ParsingUtils.createTypedInterfaceList(in, ParsedInstrumentationImpl.CREATOR); this.preferredActivityFilters = sForIntentInfoPairs.unparcel(in); - this.processes = in.readHashMap(ParsedProcess.class.getClassLoader()); + this.processes = in.readHashMap(ParsedProcessImpl.class.getClassLoader()); this.metaData = in.readBundle(boot); this.volumeUuid = sForInternedString.unparcel(in); this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class); diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java index c81d6d7d0918..07ff0ee049e1 100644 --- a/services/core/java/com/android/server/pm/permission/Permission.java +++ b/services/core/java/com/android/server/pm/permission/Permission.java @@ -28,9 +28,9 @@ import android.os.UserHandle; import android.util.Log; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.pm.PackageManagerService; import com.android.server.pm.pkg.PackageState; -import com.android.server.pm.pkg.component.ParsedPermission; import libcore.util.EmptyArray; 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 6764e087ff04..883b0666f979 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -119,6 +119,8 @@ import com.android.internal.compat.IPlatformCompat; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.IntPair; @@ -142,8 +144,6 @@ import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.SharedUserApi; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.policy.SoftRestrictedPermissionPolicy; diff --git a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java index 3a617041d55e..61677eb06fe9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionRegistry.java +++ b/services/core/java/com/android/server/pm/permission/PermissionRegistry.java @@ -18,10 +18,11 @@ package com.android.server.pm.permission; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; + import java.util.Collection; /** diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java index 91854fda09d3..4d4efac4cbe8 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -47,17 +47,17 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.security.PublicKey; diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java index 1d2c5ec2d081..20fcdb564b96 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUtils.java @@ -22,7 +22,7 @@ import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.util.SparseArray; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index cd3583b814a4..fe80f743ffc3 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -27,7 +27,7 @@ import android.os.Debug; import android.util.DebugUtils; import android.util.Slog; -import com.android.server.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; /** @hide */ public class PackageUserStateUtils { diff --git a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java index 063f577bce5f..411bdede315f 100644 --- a/services/core/java/com/android/server/pm/pkg/SharedUserApi.java +++ b/services/core/java/com/android/server/pm/pkg/SharedUserApi.java @@ -22,8 +22,8 @@ import android.content.pm.SigningDetails; import android.util.ArrayMap; import android.util.ArraySet; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.server.pm.permission.LegacyPermissionState; -import com.android.server.pm.pkg.component.ParsedProcess; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java index 1deb8d055e20..1964df0853fd 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java @@ -19,6 +19,14 @@ package com.android.server.pm.pkg.component; import android.annotation.NonNull; import android.annotation.Nullable; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; + /** * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually * be removed once all post-parsing mutation is moved to parsing. diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java index a8fb79a52837..041edaa98e63 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java @@ -29,6 +29,9 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.PackageUserState; import com.android.server.pm.pkg.PackageUserStateUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java index 68d5428d6604..f02790189cc0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java @@ -36,7 +36,7 @@ import android.os.Parcelable; import android.text.TextUtils; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; import com.android.server.pm.pkg.parsing.ParsingUtils; @@ -49,7 +49,6 @@ import java.util.Set; * @hide **/ @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedActivityImpl extends ParsedMainComponentImpl implements ParsedActivity, Parcelable { @@ -133,7 +132,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse * should be invisible to user and user should not know or see it. */ @NonNull - static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, + public static ParsedActivityImpl makeAppDetailsActivity(String packageName, String processName, int uiOptions, String taskAffinity, boolean hardwareAccelerated) { ParsedActivityImpl activity = new ParsedActivityImpl(); activity.setPackageName(packageName); @@ -700,7 +699,7 @@ public class ParsedActivityImpl extends ParsedMainComponentImpl implements Parse time = 1669437519576L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java", - inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.server.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") + inputSignatures = "private int theme\nprivate int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate int launchMode\nprivate int documentLaunchMode\nprivate int maxRecents\nprivate int configChanges\nprivate int softInputMode\nprivate int persistableMode\nprivate int lockTaskLaunchMode\nprivate int screenOrientation\nprivate int resizeMode\nprivate float maxAspectRatio\nprivate float minAspectRatio\nprivate boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate int rotationAnimation\nprivate int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java index ee793c8b2f87..5709cbb09f93 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java @@ -48,6 +48,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.ArrayUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java index 167aba301f35..cfed19aa0934 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -250,7 +251,7 @@ public class ParsedApexSystemServiceImpl implements ParsedApexSystemService, Par time = 1643723578605L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java", - inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.server.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") + inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java index ed9aa2e6860a..d3fb29b8aa66 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java @@ -25,6 +25,8 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.TextUtils; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; + import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java index b59f511afa57..62b994724346 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java @@ -22,6 +22,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedAttribution; import com.android.internal.util.DataClass; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java index 98e94c5214f0..411220ae42e8 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java @@ -26,6 +26,7 @@ import android.content.res.XmlResourceParser; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedAttribution; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java index f8d678ee39ac..512e5c7023c7 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java @@ -32,6 +32,8 @@ import android.text.TextUtils; import android.util.ArrayMap; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java index 8a0d356925d4..7bfad14d669a 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java index c63a68975588..9792a91fb699 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java @@ -26,6 +26,7 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.parsing.ParsingPackage; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java index 5b6375d6b365..ab9404310078 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.DataClass; /** diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java index 4f0a504b659f..5e67bbf4ab0b 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java @@ -31,6 +31,7 @@ import android.util.Slog; import android.util.TypedValue; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java index c670e7c1b4f7..f322eef8c3a3 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java @@ -25,6 +25,7 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java index f52ad1393878..6c22f825bab9 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java @@ -31,6 +31,8 @@ import android.os.Build; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java index 59075deb258c..afe37bc3274c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; /** @@ -174,7 +175,7 @@ public class ParsedPermissionGroupImpl extends ParsedComponentImpl implements time = 1642132854167L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java", - inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.server.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") + inputSignatures = "private int requestDetailRes\nprivate int backgroundRequestRes\nprivate int backgroundRequestDetailRes\nprivate int requestRes\nprivate int priority\npublic java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java index 4c831d36c9e3..69e33c8f281e 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java @@ -24,6 +24,8 @@ import android.text.TextUtils; import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -119,8 +121,8 @@ public class ParsedPermissionImpl extends ParsedComponentImpl implements ParsedP this.requestRes = in.readInt(); this.protectionLevel = in.readInt(); this.tree = in.readBoolean(); - this.parsedPermissionGroup = in.readParcelable(ParsedPermissionGroup.class.getClassLoader(), - ParsedPermissionGroupImpl.class); + this.parsedPermissionGroup = in.readParcelable( + ParsedPermissionGroupImpl.class.getClassLoader(), ParsedPermissionGroupImpl.class); this.knownCerts = sForStringSet.unparcel(in); } diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java index c6d1775307f8..0f2b49b8541c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java @@ -31,6 +31,8 @@ import android.util.EventLog; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java index 6d52f656b2e4..40e3670b9261 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java @@ -25,7 +25,7 @@ import android.os.Parcelable; import android.util.ArrayMap; import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -35,7 +35,6 @@ import java.util.Set; /** @hide */ @DataClass(genGetters = true, genSetters = true, genParcelable = true, genAidl = false, genBuilder = false) -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class ParsedProcessImpl implements ParsedProcess, Parcelable { @NonNull diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java index 4f4c2d5019f3..766fb90cbfa0 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProcess; import com.android.internal.util.CollectionUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java index 6f4b4c84a07e..81a3c17e2bb4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; @@ -301,7 +302,7 @@ public class ParsedProviderImpl extends ParsedMainComponentImpl implements Parse time = 1642560323360L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java", - inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.server.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") + inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java index 37bed15ba1d7..b66db4f9ced4 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java @@ -34,6 +34,7 @@ import android.os.PatternMatcher; import android.util.Slog; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java index 47e993cb02be..ca8c45d1383c 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java @@ -26,6 +26,8 @@ import android.os.Parcelable; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling.BuiltIn.ForInternedString; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java index c15266fc4cbc..1b421841f166 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java @@ -32,6 +32,7 @@ import android.content.res.XmlResourceParser; import android.os.Build; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.parsing.ParsingPackage; import com.android.server.pm.pkg.parsing.ParsingUtils; diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java index 9b89373bbb90..78377a836651 100644 --- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java +++ b/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 699ccbdc5a83..408a531eaf1d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -32,18 +32,18 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedAttribution; -import com.android.server.pm.pkg.component.ParsedInstrumentation; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; -import com.android.server.pm.pkg.component.ParsedProcess; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import java.security.PublicKey; import java.util.List; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 061698a929d6..417e3ae6e7df 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -89,6 +89,19 @@ import android.util.apk.ApkSignatureVerifier; import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedAttribution; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProcess; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.pm.SharedUidMigration; @@ -98,29 +111,17 @@ import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.component.ComponentMutateUtils; import com.android.server.pm.pkg.component.ComponentParseUtils; import com.android.server.pm.pkg.component.InstallConstraintsTagParser; -import com.android.server.pm.pkg.component.ParsedActivity; +import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.pm.pkg.component.ParsedAttributionUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoUtils; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProcessUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderUtils; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceUtils; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.split.DefaultSplitAssetLoader; import com.android.server.pm.split.SplitAssetDependencyLoader; @@ -2842,7 +2843,7 @@ public class ParsingPackageUtils { String taskAffinity = result.getResult(); // Build custom App Details activity info instead of parsing it from xml - return input.success(ParsedActivity.makeAppDetailsActivity(packageName, + return input.success(ParsedActivityImpl.makeAppDetailsActivity(packageName, pkg.getProcessName(), pkg.getUiOptions(), taskAffinity, pkg.isHardwareAccelerated())); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java index 07512855d276..2cfffb3b185d 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java @@ -30,9 +30,9 @@ import android.os.Parcelable; import android.util.Pair; import android.util.Slog; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.util.Parcelling; import com.android.internal.util.XmlUtils; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; import org.xmlpull.v1.XmlPullParserException; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java index 0ceda421913d..ed6d3b94758b 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java @@ -45,6 +45,12 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.internal.util.ArrayUtils; import com.android.server.IntentResolver; import com.android.server.pm.Computer; @@ -57,13 +63,7 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.component.ComponentMutateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.snapshot.PackageDataSnapshot; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java index b8e4c8d2a51f..0f12ee1b52fb 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverApi.java @@ -25,11 +25,11 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java index 80cde73ecf1f..2bc926c6d840 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java @@ -28,6 +28,11 @@ import android.os.UserHandle; import android.util.ArrayMap; import android.util.Pair; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedMainComponent; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.UserManagerService; @@ -36,11 +41,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedMainComponent; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.utils.WatchableImpl; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java index 0c84f4c53dfe..add33b25923e 100644 --- a/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java +++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverLocked.java @@ -24,13 +24,13 @@ import android.content.Intent; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; import com.android.server.pm.Computer; import com.android.server.pm.DumpState; import com.android.server.pm.PackageManagerTracedLock; import com.android.server.pm.UserManagerService; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedProvider; -import com.android.server.pm.pkg.component.ParsedService; import java.io.PrintWriter; import java.util.List; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index adef808bd712..735f90faee5b 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -27,11 +27,11 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Patterns; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.server.SystemConfig; import com.android.server.compat.PlatformCompat; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import java.util.List; import java.util.Objects; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 3d4d4eca7f48..6150099b2945 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.pm.pkg.component.ParsedActivity; import com.android.internal.util.CollectionUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -60,7 +61,6 @@ import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; import com.android.server.pm.pkg.PackageUserStateInternal; import com.android.server.pm.pkg.PackageUserStateUtils; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index cf1036c03c83..72c10cc9a5e8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -32,7 +32,6 @@ import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.IInputConstants.INVALID_INPUT_DEVICE_ID; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; -import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.STATE_OFF; @@ -69,6 +68,7 @@ import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; +import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE; @@ -101,6 +101,7 @@ import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManagerInternal; import android.app.ActivityTaskManager; import android.app.AppOpsManager; +import android.app.IActivityManager; import android.app.IUiModeManager; import android.app.NotificationManager; import android.app.ProgressDialog; @@ -427,6 +428,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { WindowManagerInternal mWindowManagerInternal; PowerManager mPowerManager; ActivityManagerInternal mActivityManagerInternal; + IActivityManager mActivityManagerService; ActivityTaskManagerInternal mActivityTaskManagerInternal; AutofillManagerInternal mAutofillManagerInternal; InputManager mInputManager; @@ -549,7 +551,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int mLidNavigationAccessibility; int mShortPressOnPowerBehavior; private boolean mShouldEarlyShortPressOnPower; - private boolean mShouldEarlyShortPressOnStemPrimary; + boolean mShouldEarlyShortPressOnStemPrimary; int mLongPressOnPowerBehavior; long mLongPressOnPowerAssistantTimeoutMs; int mVeryLongPressOnPowerBehavior; @@ -578,6 +580,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { private int mDoublePressOnStemPrimaryBehavior; private int mTriplePressOnStemPrimaryBehavior; private int mLongPressOnStemPrimaryBehavior; + private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp; private boolean mHandleVolumeKeysInWM; @@ -1563,7 +1566,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { ? false : mKeyguardDelegate.isShowing(); if (!keyguardActive) { - switchRecentTask(); + performStemPrimaryDoublePressSwitchToRecentTask(); } break; } @@ -1672,11 +1675,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** * Load most recent task (expect current task) and bring it to the front. */ - private void switchRecentTask() { - RecentTaskInfo targetTask = mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); + void performStemPrimaryDoublePressSwitchToRecentTask() { + RecentTaskInfo targetTask = mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp; if (targetTask == null) { if (DEBUG_INPUT) { - Slog.w(TAG, "No recent task available! Show watch face."); + Slog.w(TAG, "No recent task available! Show wallpaper."); } goHome(); return; @@ -1695,7 +1698,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { + targetTask.baseIntent); } try { - ActivityManager.getService().startActivityFromRecents(targetTask.persistentId, null); + mActivityManagerService.startActivityFromRecents(targetTask.persistentId, null); } catch (RemoteException | IllegalArgumentException e) { Slog.e(TAG, "Failed to start task " + targetTask.persistentId + " from recents", e); } @@ -2219,6 +2222,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }); } + + IActivityManager getActivityManagerService() { + return ActivityManager.getService(); + } } /** {@inheritDoc} */ @@ -2233,6 +2240,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWindowManagerFuncs = injector.getWindowManagerFuncs(); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mActivityManagerService = injector.getActivityManagerService(); mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mInputManager = mContext.getSystemService(InputManager.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); @@ -2767,8 +2775,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override void onKeyUp(long eventTime, int count) { - if (mShouldEarlyShortPressOnStemPrimary && count == 1) { - stemPrimaryPress(1 /*pressCount*/); + if (count == 1) { + // Save info about the most recent task on the first press of the stem key. This + // may be used later to switch to the most recent app using double press gesture. + // It is possible that we may navigate away from this task before the double + // press is detected, as a result of the first press, so we save the current + // most recent task before that happens. + mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp = + mActivityTaskManagerInternal.getMostRecentTaskFromBackground(); + if (mShouldEarlyShortPressOnStemPrimary) { + stemPrimaryPress(1 /*pressCount*/); + } } } } diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index dd39fb02573e..ee3b74653b75 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -32,6 +32,8 @@ import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; +import android.os.WorkDuration; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseIntArray; @@ -195,6 +197,9 @@ public final class HintManagerService extends SystemService { private static native void nativeSetMode(long halPtr, int mode, boolean enabled); + private static native void nativeReportActualWorkDuration(long halPtr, + WorkDuration[] workDurations); + /** Wrapper for HintManager.nativeInit */ public void halInit() { nativeInit(); @@ -252,6 +257,10 @@ public final class HintManagerService extends SystemService { nativeSetMode(halPtr, mode, enabled); } + /** Wrapper for HintManager.nativeReportActualWorkDuration */ + public void halReportActualWorkDuration(long halPtr, WorkDuration[] workDurations) { + nativeReportActualWorkDuration(halPtr, workDurations); + } } @VisibleForTesting @@ -624,6 +633,52 @@ public final class HintManagerService extends SystemService { } } + @Override + public void reportActualWorkDuration2(WorkDuration[] workDurations) { + synchronized (this) { + if (mHalSessionPtr == 0 || !mUpdateAllowed) { + return; + } + Preconditions.checkArgument(workDurations.length != 0, "the count" + + " of work durations shouldn't be 0."); + for (WorkDuration workDuration : workDurations) { + validateWorkDuration(workDuration); + } + mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, workDurations); + } + } + + void validateWorkDuration(WorkDuration workDuration) { + if (DEBUG) { + Slogf.d(TAG, "WorkDuration(" + workDuration.getTimestampNanos() + ", " + + workDuration.getWorkPeriodStartTimestampNanos() + ", " + + workDuration.getActualTotalDurationNanos() + ", " + + workDuration.getActualCpuDurationNanos() + ", " + + workDuration.getActualGpuDurationNanos() + ")"); + } + if (workDuration.getWorkPeriodStartTimestampNanos() <= 0) { + throw new IllegalArgumentException( + TextUtils.formatSimple( + "Work period start timestamp (%d) should be greater than 0", + workDuration.getWorkPeriodStartTimestampNanos())); + } + if (workDuration.getActualTotalDurationNanos() <= 0) { + throw new IllegalArgumentException( + TextUtils.formatSimple("Actual total duration (%d) should be greater than 0", + workDuration.getActualTotalDurationNanos())); + } + if (workDuration.getActualCpuDurationNanos() <= 0) { + throw new IllegalArgumentException( + TextUtils.formatSimple("Actual CPU duration (%d) should be greater than 0", + workDuration.getActualCpuDurationNanos())); + } + if (workDuration.getActualGpuDurationNanos() < 0) { + throw new IllegalArgumentException( + TextUtils.formatSimple("Actual GPU duration (%d) should be non negative", + workDuration.getActualGpuDurationNanos())); + } + } + private void onProcStateChanged(boolean updateAllowed) { updateHintAllowed(updateAllowed); } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 77290fd944eb..9f0a97523af8 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -603,40 +603,51 @@ public class PowerStatsService extends SystemService { @NonNull private String getEnergyConsumerName(EnergyConsumer consumer, EnergyConsumer[] energyConsumers) { - if (consumer.type != EnergyConsumerType.OTHER) { - StringBuilder sb = new StringBuilder(); - sb.append(energyConsumerTypeToString(consumer.type)); - boolean hasOrdinal = consumer.ordinal != 0; - if (!hasOrdinal) { - // See if any other EnergyConsumer of the same type has an ordinal - for (EnergyConsumer aConsumer : energyConsumers) { - if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) { - hasOrdinal = true; - break; - } + StringBuilder sb = new StringBuilder(); + switch (consumer.type) { + case EnergyConsumerType.BLUETOOTH: + sb.append("BLUETOOTH"); + break; + case EnergyConsumerType.CPU_CLUSTER: + sb.append("CPU"); + break; + case EnergyConsumerType.DISPLAY: + sb.append("DISPLAY"); + break; + case EnergyConsumerType.GNSS: + sb.append("GNSS"); + break; + case EnergyConsumerType.MOBILE_RADIO: + sb.append("MOBILE_RADIO"); + break; + case EnergyConsumerType.WIFI: + sb.append("WIFI"); + break; + case EnergyConsumerType.CAMERA: + sb.append("CAMERA"); + break; + default: + if (consumer.name != null && !consumer.name.isBlank()) { + sb.append(consumer.name.toUpperCase(Locale.ENGLISH)); + } else { + sb.append("CONSUMER_").append(consumer.type); + } + break; + } + boolean hasOrdinal = consumer.ordinal != 0; + if (!hasOrdinal) { + // See if any other EnergyConsumer of the same type has an ordinal + for (EnergyConsumer aConsumer : energyConsumers) { + if (aConsumer.type == consumer.type && aConsumer.ordinal != 0) { + hasOrdinal = true; + break; } } - if (hasOrdinal) { - sb.append('/').append(consumer.ordinal); - } - return sb.toString(); - } else { - return consumer.name; } - } - - private static String energyConsumerTypeToString(int type) { - switch(type) { - case EnergyConsumerType.BLUETOOTH: return "BLUETOOTH"; - case EnergyConsumerType.CPU_CLUSTER: return "CPU"; - case EnergyConsumerType.DISPLAY: return "DISPLAY"; - case EnergyConsumerType.GNSS: return "GNSS"; - case EnergyConsumerType.MOBILE_RADIO: return "MOBILE_RADIO"; - case EnergyConsumerType.WIFI: return "WIFI"; - case EnergyConsumerType.OTHER: return ""; - default: - throw new IllegalStateException("Unrecognized EnergyConsumerType: " + type); + if (hasOrdinal) { + sb.append('/').append(consumer.ordinal); } + return sb.toString(); } /** diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 6d580e97d578..8b57f87f5df3 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -280,12 +280,12 @@ final class SpeechRecognitionManagerServiceImpl extends return null; } - if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + if (getSessionCountByUidLocked(callingUid) == MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); Counter.logIncrementWithUid( "speech_recognition.value_exceed_session_count", callingUid); - return null; + // TODO(b/297249772): return null early to refuse the new connection } if (servicesForClient != null) { diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index fed6e7ee4686..b2e808ac8e95 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -117,16 +117,16 @@ abstract class Vibration { static final class CallerInfo { public final VibrationAttributes attrs; public final int uid; - public final int displayId; + public final int deviceId; public final String opPkg; public final String reason; - CallerInfo(@NonNull VibrationAttributes attrs, int uid, int displayId, - String opPkg, String reason) { + CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg, + String reason) { Objects.requireNonNull(attrs); this.attrs = attrs; this.uid = uid; - this.displayId = displayId; + this.deviceId = deviceId; this.opPkg = opPkg; this.reason = reason; } @@ -138,14 +138,14 @@ abstract class Vibration { CallerInfo that = (CallerInfo) o; return Objects.equals(attrs, that.attrs) && uid == that.uid - && displayId == that.displayId + && deviceId == that.deviceId && Objects.equals(opPkg, that.opPkg) && Objects.equals(reason, that.reason); } @Override public int hashCode() { - return Objects.hash(attrs, uid, displayId, opPkg, reason); + return Objects.hash(attrs, uid, deviceId, opPkg, reason); } @Override @@ -153,7 +153,7 @@ abstract class Vibration { return "CallerInfo{" + " uid=" + uid + ", opPkg=" + opPkg - + ", displayId=" + displayId + + ", deviceId=" + deviceId + ", attrs=" + attrs + ", reason=" + reason + '}'; @@ -267,8 +267,8 @@ abstract class Vibration { 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, + " | %s (uid=%d, deviceId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s", + mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.attrs.usageToString(), AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()), Long.toBinaryString(mCallerInfo.attrs.getFlags()), diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 7f55836598b5..839c2075fa8c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -61,7 +61,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; -import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -166,7 +165,6 @@ final class VibrationSettings { final MyUidObserver mUidObserver; @VisibleForTesting final SettingsBroadcastReceiver mSettingChangeReceiver; - final VirtualDeviceListener mVirtualDeviceListener; @GuardedBy("mLock") private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>(); @@ -180,6 +178,8 @@ final class VibrationSettings { @GuardedBy("mLock") @Nullable private PowerManagerInternal mPowerManagerInternal; + @Nullable + private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal; @GuardedBy("mLock") private boolean mVibrateInputDevices; @@ -207,8 +207,6 @@ final class VibrationSettings { mSettingObserver = new SettingsContentObserver(handler); mUidObserver = new MyUidObserver(); mSettingChangeReceiver = new SettingsBroadcastReceiver(); - mVirtualDeviceListener = new VirtualDeviceListener(); - mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) .getSystemUiServiceComponent().getPackageName(); @@ -272,13 +270,6 @@ final class VibrationSettings { } }); - VirtualDeviceManagerInternal vdm = LocalServices.getService( - VirtualDeviceManagerInternal.class); - if (vdm != null) { - vdm.registerVirtualDisplayListener(mVirtualDeviceListener); - vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener); - } - registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER); registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); @@ -414,8 +405,14 @@ final class VibrationSettings { && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) { return Vibration.Status.IGNORED_BACKGROUND; } - if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(callerInfo.uid, - callerInfo.displayId)) { + + if (callerInfo.deviceId != Context.DEVICE_ID_DEFAULT + && callerInfo.deviceId != Context.DEVICE_ID_INVALID) { + return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE; + } + + if (callerInfo.deviceId == Context.DEVICE_ID_INVALID + && isAppRunningOnAnyVirtualDevice(callerInfo.uid)) { return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE; } @@ -794,6 +791,15 @@ final class VibrationSettings { return out; } + private boolean isAppRunningOnAnyVirtualDevice(int uid) { + if (mVirtualDeviceManagerInternal == null) { + mVirtualDeviceManagerInternal = + LocalServices.getService(VirtualDeviceManagerInternal.class); + } + return mVirtualDeviceManagerInternal != null + && mVirtualDeviceManagerInternal.isAppRunningOnAnyVirtualDevice(uid); + } + /** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */ @VisibleForTesting final class SettingsContentObserver extends ContentObserver { @@ -853,73 +859,4 @@ final class VibrationSettings { } } } - - /** - * Implementation of Virtual Device listeners for the changes of virtual displays and of apps - * running on any virtual device. - */ - final class VirtualDeviceListener implements - VirtualDeviceManagerInternal.VirtualDisplayListener, - VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener { - @GuardedBy("mLock") - private final Set<Integer> mVirtualDisplays = new HashSet<>(); - @GuardedBy("mLock") - private final Set<Integer> mAppsOnVirtualDevice = new HashSet<>(); - - - @Override - public void onVirtualDisplayCreated(int displayId) { - synchronized (mLock) { - mVirtualDisplays.add(displayId); - } - } - - @Override - public void onVirtualDisplayRemoved(int displayId) { - synchronized (mLock) { - mVirtualDisplays.remove(displayId); - } - } - - - @Override - public void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids) { - synchronized (mLock) { - mAppsOnVirtualDevice.clear(); - mAppsOnVirtualDevice.addAll(allRunningUids); - } - } - - /** - * @param uid: uid of the calling app. - * @param displayId: the id of a Display. - * @return Returns true if: - * <ul> - * <li> the displayId is valid, and it's owned by a virtual device.</li> - * <li> the displayId is invalid, and the calling app (uid) is running on a virtual - * device.</li> - * </ul> - */ - public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) { - if (displayId == Display.DEFAULT_DISPLAY) { - // The default display is the primary physical display on the phone. - return false; - } - - synchronized (mLock) { - if (displayId == Display.INVALID_DISPLAY) { - // There is no Display object associated with the Context of calling - // {@link SystemVibratorManager}, checking the calling UID instead. - return mAppsOnVirtualDevice.contains(uid); - } else { - // Other valid display IDs representing valid logical displays will be - // checked - // against the active virtual displays set built with the registered - // {@link VirtualDisplayListener}. - return mVirtualDisplays.contains(displayId); - } - } - } - - } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index ace7777c9b58..cf33cc5f43bd 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -63,7 +63,6 @@ import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; -import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -385,7 +384,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return false; } AlwaysOnVibration alwaysOnVibration = new AlwaysOnVibration(alwaysOnId, - new Vibration.CallerInfo(attrs, uid, Display.DEFAULT_DISPLAY, opPkg, + new Vibration.CallerInfo(attrs, uid, Context.DEVICE_ID_DEFAULT, opPkg, null), effects); mAlwaysOnEffects.put(alwaysOnId, alwaysOnVibration); updateAlwaysOnLocked(alwaysOnVibration); @@ -397,16 +396,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override // Binder call - public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect, + public void vibrate(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { - vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token); + vibrateWithPermissionCheck(uid, deviceId, opPkg, effect, attrs, reason, token); } @Override // Binder call public void performHapticFeedback( - int uid, int displayId, String opPkg, int constant, boolean always, String reason, + int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token) { - performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token); + performHapticFeedbackInternal(uid, deviceId, opPkg, constant, always, reason, token); } /** @@ -417,7 +416,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @VisibleForTesting @Nullable HalVibration performHapticFeedbackInternal( - int uid, int displayId, String opPkg, int constant, boolean always, String reason, + int uid, int deviceId, String opPkg, int constant, boolean always, String reason, IBinder token) { HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider(); if (hapticVibrationProvider == null) { @@ -433,7 +432,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback( constant, /* bypassVibrationIntensitySetting= */ always); - return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs, + return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs, "performHapticFeedback: " + reason, token); } @@ -444,7 +443,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @VisibleForTesting @Nullable - HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg, + HalVibration vibrateWithPermissionCheck(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs, String reason, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason); @@ -452,24 +451,24 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { attrs = fixupVibrationAttributes(attrs, effect); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.VIBRATE, "vibrate"); - return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } - HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg, + HalVibration vibrateWithoutPermissionCheck(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs, String reason, IBinder token) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason); try { - return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token); + return vibrateInternal(uid, deviceId, opPkg, effect, attrs, reason, token); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } - private HalVibration vibrateInternal(int uid, int displayId, String opPkg, + private HalVibration vibrateInternal(int uid, int deviceId, String opPkg, @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs, String reason, IBinder token) { if (token == null) { @@ -482,7 +481,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } // Create Vibration.Stats as close to the received request as possible, for tracking. HalVibration vib = new HalVibration(token, effect, - new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason)); + new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason)); fillVibrationFallbacks(vib, effect); if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { @@ -1558,10 +1557,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private ExternalVibrationHolder(ExternalVibration externalVibration) { super(externalVibration.getToken(), new Vibration.CallerInfo( externalVibration.getVibrationAttributes(), externalVibration.getUid(), - // TODO(b/243604888): propagating displayID from IExternalVibration instead of - // using INVALID_DISPLAY for all external vibrations. - Display.INVALID_DISPLAY, - externalVibration.getPackage(), null)); + // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice + // instead of using DEVICE_ID_INVALID here and relying on the UID checks. + Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null)); this.externalVibration = externalVibration; this.scale = IExternalVibratorService.SCALE_NONE; mStatus = Vibration.Status.RUNNING; @@ -1974,8 +1972,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { boolean alreadyUnderExternalControl = false; boolean waitForCompletion = false; synchronized (mLock) { - // TODO(b/243604888): propagating displayID from IExternalVibration instead of - // using INVALID_DISPLAY for all external vibrations. Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked( vibHolder.callerInfo); @@ -2184,7 +2180,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(), - Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs, + Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); } @@ -2241,7 +2237,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { IBinder deathBinder = commonOptions.background ? VibratorManagerService.this : mShellCallbacksToken; HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(), - Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant, + Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, constant, /* always= */ commonOptions.force, /* reason= */ commonOptions.description, deathBinder); maybeWaitOnVibration(vib, commonOptions); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index bdcde66a8102..f8078d2e9f27 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -869,8 +869,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + ", reverting to built-in wallpaper!"); - int which = mWallpaper.mWhich; - clearWallpaperLocked(which, mWallpaper.userId, false, null); + clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); } } }; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index b3ae2ee30f22..b1abe2a567e8 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -558,7 +558,7 @@ final class AccessibilityController { } if (newTarget != null) { int displayId = newTarget.getDisplayId(); - IBinder clientBinder = newTarget.getIWindow().asBinder(); + IBinder clientBinder = newTarget.getWindowToken(); mFocusedWindow.put(displayId, clientBinder); } } diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index cdd1a2699e5b..3cf19ddbd89d 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -34,7 +34,6 @@ import android.os.Message; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.view.IWindow; import android.view.InputWindowHandle; import android.view.MagnificationSpec; import android.view.WindowInfo; @@ -197,14 +196,14 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { final HashMap<IBinder, Matrix> windowsTransformMatrixMap = new HashMap<>(); for (InputWindowHandle inputWindowHandle : windows) { - final IWindow iWindow = inputWindowHandle.getWindow(); - final WindowState windowState = iWindow != null ? mService.mWindowMap.get( - iWindow.asBinder()) : null; + final IBinder iWindow = inputWindowHandle.getWindowToken(); + final WindowState windowState = iWindow != null ? mService.mWindowMap.get(iWindow) + : null; if (windowState != null && windowState.shouldMagnify()) { final Matrix transformMatrix = new Matrix(); windowState.getTransformationMatrix(sTempFloats, transformMatrix); - windowsTransformMatrixMap.put(iWindow.asBinder(), transformMatrix); + windowsTransformMatrixMap.put(iWindow, transformMatrix); } } @@ -330,8 +329,8 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { // the old and new windows at the same index should be the // same, otherwise something changed. for (int i = 0; i < windowsCount; i++) { - final IWindow newWindowToken = newWindows.get(i).getWindow(); - final IWindow oldWindowToken = oldWindows.get(i).getWindow(); + final IBinder newWindowToken = newWindows.get(i).getWindowToken(); + final IBinder oldWindowToken = oldWindows.get(i).getWindowToken(); final boolean hasNewWindowToken = newWindowToken != null; final boolean hasOldWindowToken = oldWindowToken != null; @@ -342,8 +341,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { // If both old and new windows had window tokens, but those tokens differ, // then the windows have changed. - if (hasNewWindowToken && hasOldWindowToken - && !newWindowToken.asBinder().equals(oldWindowToken.asBinder())) { + if (hasNewWindowToken && hasOldWindowToken && !newWindowToken.equals(oldWindowToken)) { return true; } } @@ -393,9 +391,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { for (int index = inputWindowHandles.size() - 1; index >= 0; index--) { final Matrix windowTransformMatrix = mTempMatrix2; final InputWindowHandle windowHandle = inputWindowHandles.get(index); - final IBinder iBinder = - windowHandle.getWindow() != null ? windowHandle.getWindow().asBinder() : null; - + final IBinder iBinder = windowHandle.getWindowToken(); if (getWindowTransformMatrix(iBinder, windowTransformMatrix)) { generateMagnificationSpecInverseMatrix(windowHandle, currentMagnificationSpec, previousMagnificationSpec, windowTransformMatrix); @@ -645,7 +641,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { */ public static class AccessibilityWindow { // Data - private IWindow mWindow; + private IBinder mWindow; private int mDisplayId; @WindowManager.LayoutParams.WindowType private int mType; @@ -670,9 +666,8 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { public static AccessibilityWindow initializeData(WindowManagerService service, InputWindowHandle inputWindowHandle, Matrix magnificationInverseMatrix, IBinder pipIBinder, Matrix displayMatrix) { - final IWindow window = inputWindowHandle.getWindow(); - final WindowState windowState = window != null ? service.mWindowMap.get( - window.asBinder()) : null; + final IBinder window = inputWindowHandle.getWindowToken(); + final WindowState windowState = window != null ? service.mWindowMap.get(window) : null; final AccessibilityWindow instance = new AccessibilityWindow(); @@ -680,7 +675,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { instance.mDisplayId = inputWindowHandle.displayId; instance.mInputConfig = inputWindowHandle.inputConfig; instance.mType = inputWindowHandle.layoutParamsType; - instance.mIsPIPMenu = window != null && window.asBinder().equals(pipIBinder); + instance.mIsPIPMenu = window != null && window.equals(pipIBinder); // TODO (b/199357848): gets the private flag of the window from other way. instance.mPrivateFlags = windowState != null ? windowState.mAttrs.privateFlags : 0; @@ -867,7 +862,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { WindowInfo windowInfo = WindowInfo.obtain(); windowInfo.displayId = window.mDisplayId; windowInfo.type = window.mType; - windowInfo.token = window.mWindow != null ? window.mWindow.asBinder() : null; + windowInfo.token = window.mWindow; windowInfo.hasFlagWatchOutsideTouch = (window.mInputConfig & InputConfig.WATCH_OUTSIDE_TOUCH) != 0; // Set it to true to be consistent with the legacy implementation. @@ -878,7 +873,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { @Override public String toString() { String windowToken = - mWindow != null ? mWindow.asBinder().toString() : "(no window token)"; + mWindow != null ? mWindow.toString() : "(no window token)"; return "A11yWindow=[" + windowToken + ", displayId=" + mDisplayId + ", inputConfig=0x" + Integer.toHexString(mInputConfig) diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 26f0d34a6261..7b399c837d32 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1018,9 +1018,8 @@ class ActivityClientController extends IActivityClientController.Stub { } try { - final ClientTransaction transaction = ClientTransaction.obtain(r.app.getThread()); - transaction.addCallback(EnterPipRequestedItem.obtain(r.token)); - mService.getLifecycleManager().scheduleTransaction(transaction); + mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), + EnterPipRequestedItem.obtain(r.token)); return true; } catch (Exception e) { Slog.w(TAG, "Failed to send enter pip requested item: " diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index eeeca1018b0a..24d99387d63c 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8185,6 +8185,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * aspect ratio. */ boolean shouldCreateCompatDisplayInsets() { + if (mLetterboxUiController.shouldApplyUserFullscreenOverride()) { + // If the user has forced the applications aspect ratio to be fullscreen, don't use size + // compatibility mode in any situation. The user has been warned and therefore accepts + // the risk of the application misbehaving. + return false; + } switch (supportsSizeChanges()) { case SIZE_CHANGES_SUPPORTED_METADATA: case SIZE_CHANGES_SUPPORTED_OVERRIDE: diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 777b5cd4337b..e196d463db79 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -936,7 +936,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final int deviceId = getDeviceIdForDisplayId(r.getDisplayId()); clientTransaction.addCallback(LaunchActivityItem.obtain(r.token, - new Intent(r.intent), System.identityHashCode(r), r.info, + r.intent, System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. mergedConfiguration.getGlobalConfiguration(), diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 87ae045d4f12..43f32096b6c0 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -39,7 +39,6 @@ import android.content.res.ResourceId; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; -import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemProperties; @@ -60,7 +59,6 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.ProtoLog; -import com.android.server.LocalServices; import com.android.server.wm.utils.InsetUtils; import java.io.PrintWriter; @@ -151,97 +149,77 @@ class BackNavigationController { // Don't start any animation for it. return null; } - WindowManagerInternal windowManagerInternal = - LocalServices.getService(WindowManagerInternal.class); - IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken(); window = wmService.getFocusedWindowLocked(); if (window == null) { - EmbeddedWindowController.EmbeddedWindow embeddedWindow = - wmService.mEmbeddedWindowController.getByInputTransferToken( - focusedWindowToken); - if (embeddedWindow != null) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Current focused window is embeddedWindow. Dispatch KEYCODE_BACK."); - return null; - } - } - - // Lets first gather the states of things - // - What is our current window ? - // - Does it has an Activity and a Task ? - // TODO Temp workaround for Sysui until b/221071505 is fixed - if (window != null) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Focused window found using getFocusedWindowToken"); - } - - if (window != null) { - // This is needed to bridge the old and new back behavior with recents. While in - // Overview with live tile enabled, the previous app is technically focused but we - // add an input consumer to capture all input that would otherwise go to the apps - // being controlled by the animation. This means that the window resolved is not - // the right window to consume back while in overview, so we need to route it to - // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing - // compat callback in VRI only works when the window is focused. - // This symptom also happen while shell transition enabled, we can check that by - // isTransientLaunch to know whether the focus window is point to live tile. - final RecentsAnimationController recentsAnimationController = - wmService.getRecentsAnimationController(); - final ActivityRecord ar = window.mActivityRecord; - if ((ar != null && ar.isActivityTypeHomeOrRecents() - && ar.mTransitionController.isTransientLaunch(ar)) - || (recentsAnimationController != null - && recentsAnimationController.shouldApplyInputConsumer(ar))) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by " - + "recents. Overriding back callback to recents controller callback."); - return null; - } - - if (!window.isDrawn()) { - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Focused window didn't have a valid surface drawn."); - return null; - } - } - - if (window == null) { // We don't have any focused window, fallback ont the top currentTask of the focused // display. ProtoLog.w(WM_DEBUG_BACK_PREVIEW, "No focused window, defaulting to top current task's window"); currentTask = wmService.mAtmService.getTopDisplayFocusedRootTask(); - window = currentTask.getWindow(WindowState::isFocused); + window = currentTask != null + ? currentTask.getWindow(WindowState::isFocused) : null; + } + + if (window == null) { + Slog.e(TAG, "Window is null, returning null."); + return null; } + // This is needed to bridge the old and new back behavior with recents. While in + // Overview with live tile enabled, the previous app is technically focused but we + // add an input consumer to capture all input that would otherwise go to the apps + // being controlled by the animation. This means that the window resolved is not + // the right window to consume back while in overview, so we need to route it to + // launcher and use the legacy behavior of injecting KEYCODE_BACK since the existing + // compat callback in VRI only works when the window is focused. + // This symptom also happen while shell transition enabled, we can check that by + // isTransientLaunch to know whether the focus window is point to live tile. + final RecentsAnimationController recentsAnimationController = + wmService.getRecentsAnimationController(); + final ActivityRecord tmpAR = window.mActivityRecord; + if ((tmpAR != null && tmpAR.isActivityTypeHomeOrRecents() + && tmpAR.mTransitionController.isTransientLaunch(tmpAR)) + || (recentsAnimationController != null + && recentsAnimationController.shouldApplyInputConsumer(tmpAR))) { + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Current focused window being animated by " + + "recents. Overriding back callback to recents controller callback."); + return null; + } + + if (!window.isDrawn()) { + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "Focused window didn't have a valid surface drawn."); + return null; + } + + currentActivity = window.mActivityRecord; + currentTask = window.getTask(); + if ((currentTask != null && !currentTask.isVisibleRequested()) + || (currentActivity != null && !currentActivity.isVisibleRequested())) { + // Closing transition is happening on focus window and should be update soon, + // don't drive back navigation with it. + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Focus window is closing."); + return null; + } // Now let's find if this window has a callback from the client side. - OnBackInvokedCallbackInfo callbackInfo = null; - if (window != null) { - currentActivity = window.mActivityRecord; - currentTask = window.getTask(); - callbackInfo = window.getOnBackInvokedCallbackInfo(); - if (callbackInfo == null) { - Slog.e(TAG, "No callback registered, returning null."); - return null; - } - if (!callbackInfo.isSystemCallback()) { - backType = BackNavigationInfo.TYPE_CALLBACK; - } - infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback()); - infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback()); - mNavigationMonitor.startMonitor(window, navigationObserver); + final OnBackInvokedCallbackInfo callbackInfo = window.getOnBackInvokedCallbackInfo(); + if (callbackInfo == null) { + Slog.e(TAG, "No callback registered, returning null."); + return null; } + if (!callbackInfo.isSystemCallback()) { + backType = BackNavigationInfo.TYPE_CALLBACK; + } + infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback()); + infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback()); + mNavigationMonitor.startMonitor(window, navigationObserver); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, " + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s", currentTask, currentActivity, callbackInfo, window); - if (window == null) { - Slog.e(TAG, "Window is null, returning null."); - return null; - } - // If we don't need to set up the animation, we return early. This is the case when // - We have an application callback. // - We don't have any ActivityRecord or Task to animate. @@ -322,12 +300,13 @@ class BackNavigationController { } return false; }, currentTask, false /*includeBoundary*/, true /*traverseTopToBottom*/); - final ActivityRecord tmpPre = prevTask.getTopNonFinishingActivity(); + final ActivityRecord tmpPre = prevTask != null + ? prevTask.getTopNonFinishingActivity() : null; if (tmpPre != null) { prevActivities.add(tmpPre); findAdjacentActivityIfExist(tmpPre, prevActivities); } - if (prevActivities.isEmpty() + if (prevTask == null || prevActivities.isEmpty() || (isOccluded && !prevActivities.get(0).canShowWhenLocked())) { backType = BackNavigationInfo.TYPE_CALLBACK; } else if (prevTask.isActivityTypeHome()) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index a2f5a383caad..c2b5f88d0b4f 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -583,7 +583,7 @@ public class BackgroundActivityStartController { + " if the PI sender upgrades target_sdk to 34+! " + " (missing opt in by PI sender)! " + state.dump(resultForCaller, resultForRealCaller)); - showBalBlockedToast("BAL would be blocked", state); + showBalRiskToast("BAL would be blocked", state); return statsLog(resultForRealCaller, state); } Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed" diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 8a7cc67c3660..b34f912e1c78 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1877,15 +1877,12 @@ public class DisplayPolicy { final InsetsState insetsState = df.mInsetsState; final Rect displayFrame = insetsState.getDisplayFrame(); final Insets decor = insetsState.calculateInsets(displayFrame, - dc.mWmService.mDecorTypes, - true /* ignoreVisibility */); - final Insets statusBar = insetsState.calculateInsets(displayFrame, - Type.statusBars(), true /* ignoreVisibility */); + dc.mWmService.mDecorTypes, true /* ignoreVisibility */); + final Insets configInsets = insetsState.calculateInsets(displayFrame, + dc.mWmService.mConfigTypes, true /* ignoreVisibility */); mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom); - mConfigInsets.set(Math.max(statusBar.left, decor.left), - Math.max(statusBar.top, decor.top), - Math.max(statusBar.right, decor.right), - Math.max(statusBar.bottom, decor.bottom)); + mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right, + configInsets.bottom); mNonDecorFrame.set(displayFrame); mNonDecorFrame.inset(mNonDecorInsets); mConfigFrame.set(displayFrame); diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 14628782eac7..1670b36e4786 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -71,7 +71,7 @@ class EmbeddedWindowController { mWindowsByInputTransferToken.put(inputTransferToken, window); mWindowsByWindowToken.put(window.getWindowToken(), window); updateProcessController(window); - window.mClient.asBinder().linkToDeath(()-> { + window.mClient.linkToDeath(()-> { synchronized (mGlobalLock) { mWindows.remove(inputToken); mWindowsByInputTransferToken.remove(inputTransferToken); @@ -103,7 +103,7 @@ class EmbeddedWindowController { void remove(IWindow client) { for (int i = mWindows.size() - 1; i >= 0; i--) { EmbeddedWindow ew = mWindows.valueAt(i); - if (ew.mClient.asBinder() == client.asBinder()) { + if (ew.mClient == client.asBinder()) { mWindows.removeAt(i).onRemoved(); mWindowsByInputTransferToken.remove(ew.getInputTransferToken()); mWindowsByWindowToken.remove(ew.getWindowToken()); @@ -136,7 +136,7 @@ class EmbeddedWindowController { } static class EmbeddedWindow implements InputTarget { - final IWindow mClient; + final IBinder mClient; @Nullable final WindowState mHostWindowState; @Nullable final ActivityRecord mHostActivityRecord; final String mName; @@ -169,7 +169,7 @@ class EmbeddedWindowController { * @param windowType to forward to input * @param displayId used for focus requests */ - EmbeddedWindow(Session session, WindowManagerService service, IWindow clientToken, + EmbeddedWindow(Session session, WindowManagerService service, IBinder clientToken, WindowState hostWindowState, int ownerUid, int ownerPid, int windowType, int displayId, IBinder inputTransferToken, String inputHandleName, boolean isFocusable) { @@ -241,13 +241,8 @@ class EmbeddedWindowController { return mWmService.mRoot.getDisplayContent(getDisplayId()); } - @Override - public IWindow getIWindow() { - return mClient; - } - public IBinder getWindowToken() { - return mClient.asBinder(); + return mClient; } @Override diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 61fea4d9212d..c8fd16dbd5dc 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -57,7 +57,6 @@ import android.os.InputConfig; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; -import android.util.ArrayMap; import android.util.EventLog; import android.util.Slog; import android.view.InputChannel; @@ -74,7 +73,6 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Set; import java.util.function.Consumer; final class InputMonitor { @@ -258,7 +256,7 @@ final class InputMonitor { inputWindowHandle.setDispatchingTimeoutMillis(w.getInputDispatchingTimeoutMillis()); inputWindowHandle.setTouchOcclusionMode(w.getTouchOcclusionMode()); inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused); - inputWindowHandle.setWindowToken(w.mClient); + inputWindowHandle.setWindowToken(w.mClient.asBinder()); inputWindowHandle.setName(w.getName()); diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index 653f5f5a74e9..baf0db2e0b7e 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -16,8 +16,8 @@ package com.android.server.wm; +import android.os.IBinder; import android.util.proto.ProtoOutputStream; -import android.view.IWindow; /** * Common interface between focusable objects. @@ -33,7 +33,7 @@ interface InputTarget { int getDisplayId(); /* Client IWindow for the target. */ - IWindow getIWindow(); + IBinder getWindowToken(); /* Owning pid of the target. */ int getPid(); diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java index 90d81bd82087..b74805313d11 100644 --- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java +++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.graphics.Region; import android.os.IBinder; import android.os.InputConfig; -import android.view.IWindow; import android.view.InputApplicationHandle; import android.view.InputWindowHandle; import android.view.InputWindowHandle.InputConfigFlags; @@ -264,8 +263,8 @@ class InputWindowHandleWrapper { mChanged = true; } - void setWindowToken(IWindow windowToken) { - if (mHandle.getWindow() == windowToken) { + void setWindowToken(IBinder windowToken) { + if (mHandle.getWindowToken() == windowToken) { return; } mHandle.setWindowToken(windowToken); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 735cbc4e4287..5518de7b64fd 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -254,7 +254,9 @@ final class LetterboxUiController { // Counter for ActivityRecord#setRequestedOrientation private int mSetOrientationRequestCounter = 0; - // The min aspect ratio override set by user + // The min aspect ratio override set by user. Stores the last selected aspect ratio after + // {@link #shouldApplyUserFullscreenOverride} or {@link #shouldApplyUserMinAspectRatioOverride} + // have been invoked. @PackageManager.UserMinAspectRatio private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; @@ -661,7 +663,9 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { - if (shouldApplyUserFullscreenOverride()) { + if (shouldApplyUserFullscreenOverride() + && mActivityRecord.mDisplayContent != null + && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + mActivityRecord + " is overridden to " + screenOrientationToString(SCREEN_ORIENTATION_USER) @@ -1171,9 +1175,7 @@ final class LetterboxUiController { boolean shouldApplyUserFullscreenOverride() { if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride) || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride) - || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled() - || mActivityRecord.mDisplayContent == null - || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) { + || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()) { return false; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 0c55d8a85df1..18d64d74613f 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -894,7 +894,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public void grantInputChannel(int displayId, SurfaceControl surface, - IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, + IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int type, int inputFeatures, IBinder windowToken, IBinder inputTransferToken, String inputHandleName, InputChannel outInputChannel) { if (hostInputToken == null && !mCanAddInternalSystemWindow) { @@ -905,8 +905,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final long identity = Binder.clearCallingIdentity(); try { - mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken, - flags, mCanAddInternalSystemWindow ? privateFlags : 0, + mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken, + hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0, type, inputFeatures, windowToken, inputTransferToken, inputHandleName, outInputChannel); } finally { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3e23fab86223..f70094450abb 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -173,7 +173,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; private final Token mToken; - private IApplicationThread mRemoteAnimApp; private @Nullable ActivityRecord mPipActivity; @@ -1485,13 +1484,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mForcePlaying; } + /** Adjusts the priority of the process which will run the transition animation. */ void setRemoteAnimationApp(IApplicationThread app) { - mRemoteAnimApp = app; - } - - /** Returns the app which will run the transition animation. */ - IApplicationThread getRemoteAnimationApp() { - return mRemoteAnimApp; + final WindowProcessController wpc = mController.mAtm.getProcessController(app); + if (wpc != null) { + // This is an early prediction. If the process doesn't ack the animation in 200 ms, + // the priority will be restored. + mController.mRemotePlayer.update(wpc, true /* running */, true /* predict */); + } } void setNoAnimation(WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 1f01778acd0c..a736874f178d 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -1251,13 +1251,7 @@ class TransitionController { } else if (mPlayingTransitions.isEmpty()) { mTransitionPlayerProc.setRunningRemoteAnimation(false); mRemotePlayer.clear(); - return; } - final IApplicationThread appThread = transition.getRemoteAnimationApp(); - if (appThread == null || appThread == mTransitionPlayerProc.getThread()) return; - final WindowProcessController delegate = mAtm.getProcessController(appThread); - if (delegate == null) return; - mRemotePlayer.update(delegate, isPlaying, true /* predict */); } /** Called when a transition is aborted. This should only be called by {@link Transition} */ @@ -1483,7 +1477,7 @@ class TransitionController { * {@link #mTransitionPlayerProc}. */ static class RemotePlayer { - private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100; + private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 200; @GuardedBy("itself") private final ArrayMap<IBinder, DelegateProcess> mDelegateProcesses = new ArrayMap<>(); private final ActivityTaskManagerService mAtm; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index f6c431fe6234..2b77ffffece0 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3950,7 +3950,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return true; } - boolean useBLASTSync() { + boolean syncNextBuffer() { return mSyncState != SYNC_STATE_NONE; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0a986c8a6f2f..757d6d68c3b3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -660,11 +660,6 @@ public class WindowManagerService extends IWindowManager.Stub @NonNull final RootWindowContainer mRoot; - // Whether the system should use BLAST for ViewRootImpl - final boolean mUseBLAST; - // Whether to enable BLASTSyncEngine Transaction passing. - static final boolean USE_BLAST_SYNC = true; - final BLASTSyncEngine mSyncEngine; boolean mIsPc; @@ -1196,7 +1191,8 @@ public class WindowManagerService extends IWindowManager.Stub && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout; if (!isScreenSizeDecoupledFromStatusBarAndCutout) { mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars(); - mConfigTypes = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars(); + mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars() + | WindowInsets.Type.navigationBars(); } else { mDecorTypes = WindowInsets.Type.navigationBars(); mConfigTypes = WindowInsets.Type.navigationBars(); @@ -1219,8 +1215,6 @@ public class WindowManagerService extends IWindowManager.Stub mRoot = new RootWindowContainer(this); final ContentResolver resolver = context.getContentResolver(); - mUseBLAST = Settings.Global.getInt(resolver, - Settings.Global.DEVELOPMENT_USE_BLAST_ADAPTER_VR, 1) == 1; mSyncEngine = new BLASTSyncEngine(this); @@ -1741,13 +1735,6 @@ public class WindowManagerService extends IWindowManager.Stub } // From now on, no exceptions or errors allowed! - - res = ADD_OKAY; - - if (mUseBLAST) { - res |= WindowManagerGlobal.ADD_FLAG_USE_BLAST; - } - if (displayContent.mCurrentFocus == null) { displayContent.mWinAddedSinceNullFocus.add(win); } @@ -2555,7 +2542,7 @@ public class WindowManagerService extends IWindowManager.Stub if (outSyncIdBundle != null) { final int maybeSyncSeqId; - if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility == View.VISIBLE + if (win.syncNextBuffer() && viewVisibility == View.VISIBLE && win.mSyncSeqId > lastSyncSeqId) { maybeSyncSeqId = win.shouldSyncWithBuffers() ? win.mSyncSeqId : -1; win.markRedrawForSyncReported(); @@ -5818,15 +5805,6 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean useBLAST() { - return mUseBLAST; - } - - public boolean useBLASTSync() { - return USE_BLAST_SYNC; - } - - @Override public void getInitialDisplaySize(int displayId, Point size) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); @@ -8922,7 +8900,7 @@ public class WindowManagerService extends IWindowManager.Stub * views. */ void grantInputChannel(Session session, int callingUid, int callingPid, int displayId, - SurfaceControl surface, IWindow window, IBinder hostInputToken, + SurfaceControl surface, IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken, IBinder inputTransferToken, String inputHandleName, InputChannel outInputChannel) { final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type); @@ -8931,7 +8909,7 @@ public class WindowManagerService extends IWindowManager.Stub Objects.requireNonNull(outInputChannel); synchronized (mGlobalLock) { EmbeddedWindowController.EmbeddedWindow win = - new EmbeddedWindowController.EmbeddedWindow(session, this, window, + new EmbeddedWindowController.EmbeddedWindow(session, this, clientToken, mInputToWindowMap.get(hostInputToken), callingUid, callingPid, sanitizedType, displayId, inputTransferToken, inputHandleName, (flags & FLAG_NOT_FOCUSABLE) == 0); @@ -8943,7 +8921,7 @@ public class WindowManagerService extends IWindowManager.Stub updateInputChannel(outInputChannel.getToken(), callingUid, callingPid, displayId, surface, name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType, - null /* region */, window); + null /* region */, clientToken); } boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) { @@ -9018,10 +8996,10 @@ public class WindowManagerService extends IWindowManager.Stub private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, int displayId, SurfaceControl surface, String name, InputApplicationHandle applicationHandle, int flags, - int privateFlags, int inputFeatures, int type, Region region, IWindow window) { + int privateFlags, int inputFeatures, int type, Region region, IBinder clientToken) { final InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId); h.token = channelToken; - h.setWindowToken(window); + h.setWindowToken(clientToken); h.name = name; flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4e17011c7141..5293292d74b9 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1693,8 +1693,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override - public IWindow getIWindow() { - return mClient; + public IBinder getWindowToken() { + return mClient.asBinder(); } @Override @@ -3368,7 +3368,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mAnimatingExit = false; ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this); - if (useBLASTSync()) { + if (syncNextBuffer()) { immediatelyNotifyBlastSync(); } } @@ -5799,7 +5799,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Consume the transaction because the sync group will merge it. postDrawTransaction = null; } - } else if (useBLASTSync()) { + } else if (syncNextBuffer()) { // Sync that is not using BLAST layoutNeeded = onSyncFinishedDrawing(); } @@ -5855,7 +5855,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // drawing for being visible, then no need to request redraw. return false; } - return useBLASTSync(); + return syncNextBuffer(); } int getSyncMethod() { @@ -5880,11 +5880,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * it's next draw in to a transaction). If we have pending draw handlers, we are * looking for the client to sync. * - * See {@link WindowState#mPendingDrawHandlers} + * See {@link WindowState#mDrawHandlers} */ @Override - boolean useBLASTSync() { - return super.useBLASTSync() || (mDrawHandlers.size() != 0); + boolean syncNextBuffer() { + return super.syncNextBuffer() || (mDrawHandlers.size() != 0); } /** diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 209d93421fc1..6c15c227a3b4 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -37,7 +37,6 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.WindowContentFrameStats; -import android.view.WindowManager; import com.android.internal.protolog.common.ProtoLog; @@ -73,7 +72,7 @@ class WindowSurfaceController { mWindowSession = win.mSession; Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl"); - final SurfaceControl.Builder b = win.makeSurface() + mSurfaceControl = win.makeSurface() .setParent(win.getSurfaceControl()) .setName(name) .setFormat(format) @@ -81,16 +80,8 @@ class WindowSurfaceController { .setMetadata(METADATA_WINDOW_TYPE, windowType) .setMetadata(METADATA_OWNER_UID, mWindowSession.mUid) .setMetadata(METADATA_OWNER_PID, mWindowSession.mPid) - .setCallsite("WindowSurfaceController"); - - final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags - & WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0); - - if (useBLAST) { - b.setBLASTLayer(); - } - - mSurfaceControl = b.build(); + .setCallsite("WindowSurfaceController") + .setBLASTLayer().build(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index 7edf445d7604..ccd9bd0a50ca 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -20,6 +20,7 @@ #include <aidl/android/hardware/power/IPower.h> #include <android-base/stringprintf.h> +#include <inttypes.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <powermanager/PowerHalController.h> @@ -38,6 +39,15 @@ using android::base::StringPrintf; namespace android { +static struct { + jclass clazz{}; + jfieldID workPeriodStartTimestampNanos{}; + jfieldID actualTotalDurationNanos{}; + jfieldID actualCpuDurationNanos{}; + jfieldID actualGpuDurationNanos{}; + jfieldID timestampNanos{}; +} gWorkDurationInfo; + static power::PowerHalController gPowerHalController; static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap; static std::mutex gSessionMapLock; @@ -180,6 +190,26 @@ static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, ji setMode(session_ptr, static_cast<SessionMode>(mode), enabled); } +static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, + jobjectArray jWorkDurations) { + int size = env->GetArrayLength(jWorkDurations); + std::vector<WorkDuration> workDurations(size); + for (int i = 0; i < size; i++) { + jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); + workDurations[i].workPeriodStartTimestampNanos = + env->GetLongField(workDuration, gWorkDurationInfo.workPeriodStartTimestampNanos); + workDurations[i].durationNanos = + env->GetLongField(workDuration, gWorkDurationInfo.actualTotalDurationNanos); + workDurations[i].cpuDurationNanos = + env->GetLongField(workDuration, gWorkDurationInfo.actualCpuDurationNanos); + workDurations[i].gpuDurationNanos = + env->GetLongField(workDuration, gWorkDurationInfo.actualGpuDurationNanos); + workDurations[i].timeStampNanos = + env->GetLongField(workDuration, gWorkDurationInfo.timestampNanos); + } + reportActualWorkDuration(session_ptr, workDurations); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sHintManagerServiceMethods[] = { /* name, signature, funcPtr */ @@ -194,9 +224,23 @@ static const JNINativeMethod sHintManagerServiceMethods[] = { {"nativeSendHint", "(JI)V", (void*)nativeSendHint}, {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads}, {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode}, + {"nativeReportActualWorkDuration", "(J[Landroid/os/WorkDuration;)V", + (void*)nativeReportActualWorkDuration2}, }; int register_android_server_HintManagerService(JNIEnv* env) { + gWorkDurationInfo.clazz = env->FindClass("android/os/WorkDuration"); + gWorkDurationInfo.workPeriodStartTimestampNanos = + env->GetFieldID(gWorkDurationInfo.clazz, "mWorkPeriodStartTimestampNanos", "J"); + gWorkDurationInfo.actualTotalDurationNanos = + env->GetFieldID(gWorkDurationInfo.clazz, "mActualTotalDurationNanos", "J"); + gWorkDurationInfo.actualCpuDurationNanos = + env->GetFieldID(gWorkDurationInfo.clazz, "mActualCpuDurationNanos", "J"); + gWorkDurationInfo.actualGpuDurationNanos = + env->GetFieldID(gWorkDurationInfo.clazz, "mActualGpuDurationNanos", "J"); + gWorkDurationInfo.timestampNanos = + env->GetFieldID(gWorkDurationInfo.clazz, "mTimestampNanos", "J"); + return jniRegisterNativeMethods(env, "com/android/server/power/hint/" "HintManagerService$NativeWrapper", diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 215934f4dc09..cca4261795ab 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -455,6 +455,20 @@ <xs:annotation name="nullable"/> <xs:annotation name="final"/> </xs:element> + <!-- list of supported modes when sensor is ON. Each point corresponds to one mode. + Mode format is : first = refreshRate, second = vsyncRate. E.g. : + <supportedModes> + <point> + <first>60</first> // refreshRate + <second>60</second> //vsyncRate + </point> + .... + </supportedModes> + --> + <xs:element type="nonNegativeFloatToFloatMap" name="supportedModes" minOccurs="0"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> </xs:sequence> </xs:complexType> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index f7e004375071..f767291b4953 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -349,9 +349,11 @@ package com.android.server.display.config { ctor public SensorDetails(); method @Nullable public final String getName(); method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate(); + method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getSupportedModes(); method @Nullable public final String getType(); method public final void setName(@Nullable String); method public final void setRefreshRate(@Nullable com.android.server.display.config.RefreshRateRange); + method public final void setSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap); method public final void setType(@Nullable String); } diff --git a/services/proguard.flags b/services/proguard.flags index 261bb7cacdc4..88561b460b05 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -14,13 +14,20 @@ } # APIs referenced by dependent JAR files and modules --keep @interface android.annotation.SystemApi +# TODO(b/300514883): Pull @SystemApi keep rules from system-api.pro. +-keep interface android.annotation.SystemApi -keep @android.annotation.SystemApi class * { public protected *; } -keepclasseswithmembers class * { @android.annotation.SystemApi *; } +# Also ensure nested classes are kept. This is overly conservative, but handles +# cases where such classes aren't explicitly marked @SystemApi. +-if @android.annotation.SystemApi class * +-keep public class <1>$** { + public protected *; +} # Derivatives of SystemService and other services created via reflection -keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService { @@ -38,10 +45,6 @@ public static void write(...); } -# Binder interfaces --keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface --keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface - # Various classes subclassed in or referenced via JNI in ethernet-service -keep public class android.net.** { *; } -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; } diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 12cd0f6e1a7c..8d76fddcc793 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -24,6 +24,7 @@ import android.content.pm.PackageManager import android.os.Binder import android.os.UserHandle import android.util.ArrayMap +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.AppsFilterImpl import com.android.server.pm.PackageManagerService import com.android.server.pm.PackageManagerServiceInjector @@ -39,7 +40,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackageInternal import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.parsing.pkg.ParsedPackage import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.resolution.ComponentResolver import com.android.server.pm.snapshot.PackageDataSnapshot import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index d5cd6ef9eb69..25146a87970f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -49,16 +49,16 @@ import android.util.SparseArray; import androidx.annotation.NonNull; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.server.om.OverlayReferenceMapper; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; import com.android.server.pm.pkg.component.ParsedComponentImpl; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedProviderImpl; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index 0eac4e6a25ac..7c28e13f0eee 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -58,6 +58,16 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.pm.pkg.component.ParsedActivity; +import com.android.internal.pm.pkg.component.ParsedApexSystemService; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedInstrumentation; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; +import com.android.internal.pm.pkg.component.ParsedPermissionGroup; +import com.android.internal.pm.pkg.component.ParsedProvider; +import com.android.internal.pm.pkg.component.ParsedService; +import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -69,24 +79,14 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.permission.CompatibilityPermissionInfo; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageUserStateInternal; -import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedActivityImpl; -import com.android.server.pm.pkg.component.ParsedApexSystemService; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import com.android.server.pm.pkg.component.ParsedInstrumentationImpl; -import com.android.server.pm.pkg.component.ParsedIntentInfo; import com.android.server.pm.pkg.component.ParsedIntentInfoImpl; -import com.android.server.pm.pkg.component.ParsedPermission; -import com.android.server.pm.pkg.component.ParsedPermissionGroup; import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl; import com.android.server.pm.pkg.component.ParsedPermissionImpl; import com.android.server.pm.pkg.component.ParsedPermissionUtils; -import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedProviderImpl; -import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedServiceImpl; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.server.pm.pkg.parsing.ParsingPackage; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java index 9e371649214c..7123c2076640 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java @@ -38,16 +38,16 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.pm.test.service.server.R; +import com.android.internal.pm.pkg.component.ParsedComponent; +import com.android.internal.pm.pkg.component.ParsedIntentInfo; +import com.android.internal.pm.pkg.component.ParsedPermission; import com.android.internal.util.ArrayUtils; import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.component.ParsedActivityUtils; -import com.android.server.pm.pkg.component.ParsedComponent; -import com.android.server.pm.pkg.component.ParsedIntentInfo; -import com.android.server.pm.pkg.component.ParsedPermission; import com.android.server.pm.pkg.component.ParsedPermissionUtils; +import com.android.server.pm.test.service.server.R; import com.google.common.truth.Expect; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt index 0e2e35f25b15..26468544e8d6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.ActivityInfo -import com.android.server.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt index 4e44e96aa710..52d5b3bccb72 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedAttribution +import com.android.internal.pm.pkg.component.ParsedAttribution import com.android.server.pm.pkg.component.ParsedAttributionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt index 058f6d69f3e7..af0c0de2db15 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt @@ -17,7 +17,7 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PackageManager -import com.android.server.pm.pkg.component.ParsedComponent +import com.android.internal.pm.pkg.component.ParsedComponent import com.android.server.pm.pkg.component.ParsedComponentImpl import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Bundle diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt index eeb30b70c143..dc0f194f10cc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedInstrumentationImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt index f27a51f63049..5224f23d38d1 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedIntentInfo +import com.android.internal.pm.pkg.component.ParsedIntentInfo import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.os.Parcelable import android.os.PatternMatcher diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt index a0d8c44899d8..dfff6025e2eb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedMainComponent +import com.android.internal.pm.pkg.component.ParsedMainComponent import com.android.server.pm.pkg.component.ParsedMainComponentImpl import android.os.Parcelable import java.util.Arrays diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt index f266e7616ff3..ccbf558734d3 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt index c72a44e4c4e0..2814783b6849 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt @@ -16,8 +16,8 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl import com.android.server.pm.pkg.component.ParsedPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt index 8b9361a31d0a..2e9604696acb 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl import android.util.ArrayMap import kotlin.contracts.ExperimentalContracts @@ -45,7 +45,7 @@ class ParsedProcessTest : ParcelableComponentTest(ParsedProcess::class, ParsedPr override fun extraParams() = listOf( getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")), getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply { - put("package1", "classname1"); + put("package1", "classname1") }), ) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt index 0302d5785d8f..290dbd6277b6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt @@ -17,14 +17,14 @@ package com.android.server.pm.test.parsing.parcelling import android.content.pm.PathPermission -import com.android.server.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl import android.os.PatternMatcher import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) { - +class ParsedProviderTest : ParsedMainComponentTest(ParsedProvider::class, ParsedProviderImpl::class) +{ override val defaultImpl = ParsedProviderImpl() override val creator = ParsedProviderImpl.CREATOR diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt index e2c9439df9cf..3ae7e9220cc6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedService +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.pkg.component.ParsedServiceImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt index ad607366967f..67dfc6d4f7ef 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt @@ -16,7 +16,7 @@ package com.android.server.pm.test.parsing.parcelling -import com.android.server.pm.pkg.component.ParsedUsesPermission +import com.android.internal.pm.pkg.component.ParsedUsesPermission import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import kotlin.contracts.ExperimentalContracts diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index d217d63c5b44..1da3a2234ffc 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -24,26 +24,25 @@ import android.content.pm.SharedLibraryInfo import android.content.pm.VersionedPackage import android.os.PatternMatcher import android.util.ArraySet +import com.android.internal.pm.pkg.component.ParsedActivity +import com.android.internal.pm.pkg.component.ParsedInstrumentation +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup +import com.android.internal.pm.pkg.component.ParsedProcess +import com.android.internal.pm.pkg.component.ParsedProvider +import com.android.internal.pm.pkg.component.ParsedService import com.android.server.pm.PackageSetting import com.android.server.pm.PackageSettingBuilder import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.PackageUserStateImpl -import com.android.server.pm.pkg.component.ParsedActivity import com.android.server.pm.pkg.component.ParsedActivityImpl import com.android.server.pm.pkg.component.ParsedComponentImpl -import com.android.server.pm.pkg.component.ParsedInstrumentation import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.pm.pkg.component.ParsedPermissionImpl -import com.android.server.pm.pkg.component.ParsedProcess import com.android.server.pm.pkg.component.ParsedProcessImpl -import com.android.server.pm.pkg.component.ParsedProvider import com.android.server.pm.pkg.component.ParsedProviderImpl -import com.android.server.pm.pkg.component.ParsedService import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest import com.google.common.truth.Expect import org.junit.Rule diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt index ec84bc329674..316f3387c539 100644 --- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt +++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/BaseAppIdPermissionPolicyTest.kt @@ -26,6 +26,8 @@ import android.os.Bundle import android.util.ArrayMap import android.util.SparseArray import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.internal.pm.pkg.component.ParsedPermission +import com.android.internal.pm.pkg.component.ParsedPermissionGroup import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.server.extendedtestutils.wheneverStatic import com.android.server.permission.access.MutableAccessState @@ -39,8 +41,6 @@ import com.android.server.pm.parsing.PackageInfoUtils import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.pm.pkg.PackageUserState -import com.android.server.pm.pkg.component.ParsedPermission -import com.android.server.pm.pkg.component.ParsedPermissionGroup import com.android.server.testutils.any import com.android.server.testutils.mock import com.android.server.testutils.whenever diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 179a9d5f748e..0bcbeb9b8a85 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -17,9 +17,12 @@ package com.android.server.display; +import static com.android.server.display.config.SensorData.SupportedMode; import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat; import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -526,6 +529,26 @@ public final class DisplayDeviceConfigTest { } @Test + public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile( + getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(), + /* includeIdleMode= */ true)); + assertEquals("test_proximity_sensor", + mDisplayDeviceConfig.getProximitySensor().type); + assertEquals("Test Proximity Sensor", + mDisplayDeviceConfig.getProximitySensor().name); + assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA); + assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA); + assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2); + SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0); + assertEquals(mode.refreshRate, 60, SMALL_DELTA); + assertEquals(mode.vsyncRate, 65, SMALL_DELTA); + mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1); + assertEquals(mode.refreshRate, 120, SMALL_DELTA); + assertEquals(mode.vsyncRate, 125, SMALL_DELTA); + } + + @Test public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(); @@ -821,6 +844,27 @@ public final class DisplayDeviceConfigTest { + "</proxSensor>\n"; } + private String getValidProxSensorWithRefreshRateAndVsyncRate() { + return "<proxSensor>\n" + + "<type>test_proximity_sensor</type>\n" + + "<name>Test Proximity Sensor</name>\n" + + "<refreshRate>\n" + + "<minimum>60</minimum>\n" + + "<maximum>90</maximum>\n" + + "</refreshRate>\n" + + "<supportedModes>\n" + + "<point>\n" + + "<first>60</first>\n" // refreshRate + + "<second>65</second>\n" //vsyncRate + + "</point>\n" + + "<point>\n" + + "<first>120</first>\n" // refreshRate + + "<second>125</second>\n" //vsyncRate + + "</point>\n" + + "</supportedModes>" + + "</proxSensor>\n"; + } + private String getProxSensorWithEmptyValues() { return "<proxSensor>\n" + "<type></type>\n" diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 9684f427adb3..3775ac942579 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -123,6 +123,7 @@ import com.android.server.SystemService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; +import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.input.InputManagerInternal; @@ -2317,11 +2318,8 @@ public class DisplayManagerServiceTest { String testSensorType = "testType"; Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName); - DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData(); - sensorData.type = testSensorType; - sensorData.name = testSensorName; - sensorData.minRefreshRate = 10f; - sensorData.maxRefreshRate = 100f; + SensorData sensorData = new SensorData(testSensorType, testSensorName, + /* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f); when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData); when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList( @@ -2352,12 +2350,6 @@ public class DisplayManagerServiceTest { String testSensorType = "testType"; Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName); - DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData(); - sensorData.type = testSensorType; - sensorData.name = testSensorName; - sensorData.minRefreshRate = 10f; - sensorData.maxRefreshRate = 100f; - when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(null); when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList( testSensor)); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index 47521d13e49c..57f392a5887f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -77,6 +77,7 @@ import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.layout.Layout; @@ -1618,23 +1619,13 @@ public final class DisplayPowerController2Test { when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); when(displayDeviceConfigMock.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( - new DisplayDeviceConfig.SensorData()); + new SensorData()); when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_LIGHT; - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_LIGHT, null)); when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) .thenReturn(new int[0]); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 37ee23f38b14..9617bd08fd93 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -76,6 +76,7 @@ import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; import com.android.server.display.layout.Layout; @@ -1515,23 +1516,13 @@ public final class DisplayPowerControllerTest { when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); when(displayDeviceConfigMock.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( - new DisplayDeviceConfig.SensorData()); + new SensorData()); when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_LIGHT; - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_LIGHT, null)); when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) .thenReturn(new int[0]); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java index 534a708af3c7..ebd6614aba14 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java @@ -37,6 +37,7 @@ import android.view.Display; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.server.display.config.SensorData; import com.android.server.testutils.OffsettableClock; import org.junit.Before; @@ -74,14 +75,7 @@ public final class DisplayPowerProximityStateControllerTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); when(mDisplayDeviceConfig.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - // This is kept null because currently there is no way to define a sensor - // name in TestUtils - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); setUpProxSensor(); DisplayPowerProximityStateController.Injector injector = new DisplayPowerProximityStateController.Injector() { @@ -171,13 +165,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = null; - name = null; - } - }); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(), mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY, @@ -188,13 +176,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault() throws Exception { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = null; - name = null; - } - }); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn( TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity")); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( @@ -207,13 +189,7 @@ public final class DisplayPowerProximityStateControllerTest { @Test public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay() throws Exception { - when(mDisplayDeviceConfig.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = null; - name = null; - } - }); + when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData()); when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn( TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity")); mDisplayPowerProximityStateController = new DisplayPowerProximityStateController( @@ -240,12 +216,7 @@ public final class DisplayPowerProximityStateControllerTest { public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception { DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class); when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - name = null; - } - }); + new SensorData(Sensor.STRING_TYPE_PROXIMITY, null)); Sensor newProxSensor = TestUtils.createSensor( Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f); when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL))) diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 6a95d5c57024..499e7008febd 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -229,7 +229,19 @@ public class DisplayModeDirectorTest { LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight(), 0, APP_MODE_65.getRefreshRate())), - /*displayResolutionRangeVotingEnabled*/ true}}); + /*displayResolutionRangeVotingEnabled*/ true}, + {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), + /*expectedPhysicalRefreshRate*/ 64.99f, + /*expectedAppRequestedRefreshRate*/ 64.99f, + /*votesWithPriorities*/ Map.of( + Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, + Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), + Vote.PRIORITY_APP_REQUEST_SIZE, + Vote.forSize(APP_MODE_65.getPhysicalWidth(), + APP_MODE_65.getPhysicalHeight()), + Vote.PRIORITY_LOW_POWER_MODE, + Vote.forPhysicalRefreshRates( + 0, 64.99f))}}); final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java index 4494b0c412dc..6e2d954c05a8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java @@ -28,7 +28,7 @@ import android.hardware.input.InputSensorInfo; import androidx.test.filters.SmallTest; import com.android.internal.annotations.Keep; -import com.android.server.display.DisplayDeviceConfig.SensorData; +import com.android.server.display.config.SensorData; import org.junit.Before; import org.junit.Test; @@ -123,9 +123,7 @@ public class SensorUtilsTest { when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors); when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor); - SensorData sensorData = new SensorData(); - sensorData.name = sensorName; - sensorData.type = sensorType; + SensorData sensorData = new SensorData(sensorType, sensorName); Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType); 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 37fe8d1d7fff..f45dd391fbc7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -2625,7 +2625,7 @@ public class MockingOomAdjusterTests { doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); doReturn(app).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(app); assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); @@ -2637,7 +2637,7 @@ public class MockingOomAdjusterTests { // Since sr.app is null, this service cannot be in the same process as the // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect. app.mServices.updateHasAboveClientLocked(); - sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); + updateOomAdj(app); assertTrue(app.mServices.hasAboveClient()); assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); } diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index df46054f0f6f..1838fe884561 100644 --- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -1081,7 +1081,7 @@ public class PowerStatsServiceTest { assertThat(result.powerMonitors).isNotNull(); assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList()) .containsAtLeast( - "energyconsumer0", + "ENERGYCONSUMER0", "BLUETOOTH/1", "[channelname0]:channelsubsystem0", "[channelname1]:channelsubsystem1"); @@ -1131,7 +1131,7 @@ public class PowerStatsServiceTest { Map<String, PowerMonitor> map = Arrays.stream(supportedPowerMonitorsResult.powerMonitors) .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm)); - PowerMonitor consumer1 = map.get("energyconsumer0"); + PowerMonitor consumer1 = map.get("ENERGYCONSUMER0"); PowerMonitor consumer2 = map.get("BLUETOOTH/1"); PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0"); PowerMonitor measurement2 = map.get("[channelname1]:channelsubsystem1"); @@ -1196,6 +1196,6 @@ public class PowerStatsServiceTest { supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult(); mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult); assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors) - .map(PowerMonitor::getName).toList()).contains("energyconsumer0"); + .map(PowerMonitor::getName).toList()).contains("ENERGYCONSUMER0"); } } diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml index ef19ba11fb6d..e89199dc9278 100644 --- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml +++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml @@ -39,6 +39,7 @@ crossProfileIntentResolutionStrategy='0' mediaSharedWithParent='true' credentialShareableWithParent='false' + authAlwaysRequiredToDisableQuietMode='true' showInSettings='23' hideInSettingsInQuietMode='true' inheritDevicePolicy='450' diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java index 860819911173..cfd0289e5650 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationConnectionWrapperTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionWrapperTest.java @@ -37,11 +37,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Tests for WindowMagnificationConnectionWrapper. We don't test {@code - * WindowMagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in + * Tests for MagnificationConnectionWrapper. We don't test {@code + * MagnificationConnectionWrapper#linkToDeath(IBinder.DeathRecipient)} since it's tested in * {@link WindowMagnificationManagerTest}. */ -public class WindowMagnificationConnectionWrapperTest { +public class MagnificationConnectionWrapperTest { private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @@ -54,14 +54,14 @@ public class WindowMagnificationConnectionWrapperTest { private MagnificationAnimationCallback mAnimationCallback; private MockWindowMagnificationConnection mMockWindowMagnificationConnection; - private WindowMagnificationConnectionWrapper mConnectionWrapper; + private MagnificationConnectionWrapper mConnectionWrapper; @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mMockWindowMagnificationConnection = new MockWindowMagnificationConnection(); mConnection = mMockWindowMagnificationConnection.getConnection(); - mConnectionWrapper = new WindowMagnificationConnectionWrapper(mConnection, mTrace); + mConnectionWrapper = new MagnificationConnectionWrapper(mConnection, mTrace); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java deleted file mode 100644 index 8f77e9b8d523..000000000000 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualCameraTest.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.virtual; - -import static com.google.common.truth.Truth.assertThat; - -import android.Manifest; -import android.companion.virtual.VirtualDeviceParams; -import android.companion.virtual.camera.IVirtualCamera; -import android.companion.virtual.camera.IVirtualCameraSession; -import android.companion.virtual.camera.VirtualCamera; -import android.companion.virtual.camera.VirtualCameraConfig; -import android.companion.virtual.camera.VirtualCameraHalConfig; -import android.companion.virtual.camera.VirtualCameraSession; -import android.companion.virtual.camera.VirtualCameraStreamConfig; -import android.companion.virtual.flags.Flags; -import android.content.ComponentName; -import android.graphics.ImageFormat; -import android.os.Handler; -import android.os.HandlerExecutor; -import android.os.Looper; -import android.os.RemoteException; -import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.testing.AndroidTestingRunner; -import android.testing.TestableContext; -import android.testing.TestableLooper; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.compatibility.common.util.AdoptShellPermissionsRule; -import com.android.server.companion.virtual.camera.IVirtualCameraService; -import com.android.server.companion.virtual.camera.VirtualCameraController; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.HashSet; -import java.util.Set; - -@Presubmit -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class VirtualCameraTest { - - private static final String PKG = "com.android.virtualcamera"; - private static final String CLS = ".VirtualCameraService"; - public static final String CAMERA_DISPLAY_NAME = "testCamera"; - - private final TestableContext mContext = - new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); - private FakeVirtualCameraService mFakeVirtualCameraService; - private VirtualCameraController mVirtualCameraController; - - @Rule public final VirtualDeviceRule mVirtualDeviceRule = new VirtualDeviceRule(mContext); - - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule - public AdoptShellPermissionsRule mAdoptShellPermissionsRule = - new AdoptShellPermissionsRule( - InstrumentationRegistry.getInstrumentation().getUiAutomation(), - Manifest.permission.CREATE_VIRTUAL_DEVICE); - - @Before - public void setUp() { - mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> mVirtualCameraController); - mFakeVirtualCameraService = new FakeVirtualCameraService(); - connectFakeService(); - mVirtualCameraController = new VirtualCameraController(mContext); - } - - private VirtualDeviceImpl createVirtualDevice() { - return mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build()); - } - - private void connectFakeService() { - mContext.addMockService( - ComponentName.createRelative(PKG, CLS), mFakeVirtualCameraService.asBinder()); - } - - @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) - @Test - public void addVirtualCamera() { - VirtualDeviceImpl virtualDevice = createVirtualDevice(); - VirtualCameraConfig config = createVirtualCameraConfig(null); - IVirtualCamera.Default camera = new IVirtualCamera.Default(); - virtualDevice.registerVirtualCamera(camera); - - assertThat(mFakeVirtualCameraService.mCameras).contains(camera); - } - - @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) - @Test - public void addVirtualCamera_serviceNotReady() { - TestableContext context = - new TestableContext(InstrumentationRegistry.getInstrumentation().getContext()); - VirtualCameraController virtualCameraController = new VirtualCameraController(context); - mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> virtualCameraController); - - VirtualDeviceImpl virtualDevice = - mVirtualDeviceRule.createVirtualDevice(new VirtualDeviceParams.Builder().build()); - IVirtualCamera.Default camera = new IVirtualCamera.Default(); - VirtualCameraConfig config = createVirtualCameraConfig(null); - virtualDevice.registerVirtualCamera(camera); - FakeVirtualCameraService fakeVirtualCameraService = new FakeVirtualCameraService(); - - // Only add the service after connecting the camera - virtualCameraController.onServiceConnected( - ComponentName.createRelative(PKG, CLS), fakeVirtualCameraService.asBinder()); - - assertThat(fakeVirtualCameraService.mCameras).contains(camera); - } - - @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) - @Test - public void getCameraConfiguration() { - VirtualDeviceImpl virtualDevice = createVirtualDevice(); - VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {}; - VirtualCameraConfig config = - new VirtualCameraConfig.Builder() - .addStreamConfiguration(10, 10, ImageFormat.RGB_565) - .setDisplayName(CAMERA_DISPLAY_NAME) - .setCallback( - new HandlerExecutor(new Handler(Looper.getMainLooper())), - () -> virtualCameraSession) - .build(); - - VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config); - - VirtualCameraConfig returnedConfig = virtualCamera.getConfig(); - assertThat(returnedConfig).isNotNull(); - assertThat(returnedConfig.getDisplayName()).isEqualTo(CAMERA_DISPLAY_NAME); - Set<VirtualCameraStreamConfig> streamConfigs = returnedConfig.getStreamConfigs(); - assertThat(streamConfigs).hasSize(1); - VirtualCameraStreamConfig streamConfig = - streamConfigs.toArray(new VirtualCameraStreamConfig[0])[0]; - assertThat(streamConfig.format).isEqualTo(ImageFormat.RGB_565); - assertThat(streamConfig.width).isEqualTo(10); - assertThat(streamConfig.height).isEqualTo(10); - - VirtualCameraHalConfig halConfig = virtualCamera.getHalConfig(); - assertThat(halConfig).isNotNull(); - assertThat(halConfig.displayName).isEqualTo(CAMERA_DISPLAY_NAME); - assertThat(halConfig.streamConfigs).asList().hasSize(1); - assertThat(halConfig.streamConfigs[0].format).isEqualTo(ImageFormat.RGB_565); - assertThat(halConfig.streamConfigs[0].width).isEqualTo(10); - assertThat(halConfig.streamConfigs[0].height).isEqualTo(10); - } - - @RequiresFlagsEnabled(Flags.FLAG_VIRTUAL_CAMERA) - @Test - public void createCameraWithVirtualCameraInstance() { - VirtualDeviceImpl virtualDevice = createVirtualDevice(); - - VirtualCameraSession virtualCameraSession = new VirtualCameraSession() {}; - VirtualCameraConfig config = createVirtualCameraConfig(virtualCameraSession); - VirtualCamera virtualCamera = new VirtualCamera(virtualDevice, config); - - assertThat(mFakeVirtualCameraService.mCameras).contains(virtualCamera); - assertThat(virtualCamera.open()).isInstanceOf(IVirtualCameraSession.class); - } - - @RequiresFlagsDisabled(Flags.FLAG_VIRTUAL_CAMERA) - @Test - public void createCameraDoesNothingWhenControllerIsNull() { - mVirtualDeviceRule.withVirtualCameraControllerSupplier(() -> null); - VirtualDeviceImpl virtualDevice = createVirtualDevice(); - IVirtualCamera.Default camera = new IVirtualCamera.Default(); - VirtualCameraConfig config = createVirtualCameraConfig(null); - virtualDevice.registerVirtualCamera(camera); - - assertThat(mFakeVirtualCameraService.mCameras).doesNotContain(camera); - } - - @NonNull - private static VirtualCameraConfig createVirtualCameraConfig( - VirtualCameraSession virtualCameraSession) { - return new VirtualCameraConfig.Builder() - .addStreamConfiguration(10, 10, ImageFormat.RGB_565) - .setDisplayName(CAMERA_DISPLAY_NAME) - .setCallback( - new HandlerExecutor(new Handler(Looper.getMainLooper())), - () -> virtualCameraSession) - .build(); - } - - private static class FakeVirtualCameraService extends IVirtualCameraService.Stub { - - final Set<IVirtualCamera> mCameras = new HashSet<>(); - - @Override - public boolean registerCamera(IVirtualCamera camera) throws RemoteException { - mCameras.add(camera); - return true; - } - - @Override - public void unregisterCamera(IVirtualCamera camera) throws RemoteException { - mCameras.remove(camera); - } - } -} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 2598a6bea1c0..30300ec3ad2e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -52,9 +52,11 @@ import android.Manifest; import android.app.WindowConfiguration; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; +import android.companion.AssociationRequest; import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceIntentInterceptor; import android.companion.virtual.IVirtualDeviceSoundEffectListener; +import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -134,6 +136,8 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.function.Consumer; @@ -259,10 +263,10 @@ public class VirtualDeviceManagerServiceTest { @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @Mock - private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener; - @Mock private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener mAppsOnVirtualDeviceListener; @Mock + private Consumer<String> mPersistentDeviceIdRemovedListener; + @Mock IPowerManager mIPowerManagerMock; @Mock IThermalService mIThermalServiceMock; @@ -374,9 +378,8 @@ public class VirtualDeviceManagerServiceTest { mCameraAccessController = new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback); - mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null, - null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, - 0, 0, -1); + mAssociationInfo = createAssociationInfo( + /* associationId= */ 1, AssociationRequest.DEVICE_PROFILE_APP_STREAMING); mVdms = new VirtualDeviceManagerService(mContext); mLocalService = mVdms.getLocalServiceInstance(); @@ -724,25 +727,36 @@ public class VirtualDeviceManagerServiceTest { } @Test - public void onVirtualDisplayCreatedLocked_listenersNotified() { - mLocalService.registerVirtualDisplayListener(mDisplayListener); - - mLocalService.onVirtualDisplayCreated(DISPLAY_ID_1); + public void onPersistentDeviceIdsRemoved_listenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mLocalService.onPersistentDeviceIdsRemoved(Set.of(mDeviceImpl.getPersistentDeviceId())); TestableLooper.get(this).processAllMessages(); - verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); } @Test - public void onVirtualDisplayRemovedLocked_listenersNotified() { - mLocalService.registerVirtualDisplayListener(mDisplayListener); + public void onCdmAssociationsChanged_persistentDeviceIdRemovedListenersNotified() { + mLocalService.registerPersistentDeviceIdRemovedListener(mPersistentDeviceIdRemovedListener); + mVdms.onCdmAssociationsChanged(List.of(mAssociationInfo)); + TestableLooper.get(this).processAllMessages(); - addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1); + mVdms.onCdmAssociationsChanged(List.of( + createAssociationInfo(2, AssociationRequest.DEVICE_PROFILE_APP_STREAMING), + createAssociationInfo(3, AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION), + createAssociationInfo(4, AssociationRequest.DEVICE_PROFILE_WATCH))); + TestableLooper.get(this).processAllMessages(); - mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener).accept(mDeviceImpl.getPersistentDeviceId()); + + mVdms.onCdmAssociationsChanged(Collections.emptyList()); TestableLooper.get(this).processAllMessages(); - verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID_1); + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(2)); + verify(mPersistentDeviceIdRemovedListener) + .accept(VirtualDeviceImpl.createPersistentDeviceId(3)); + verifyNoMoreInteractions(mPersistentDeviceIdRemovedListener); } @Test @@ -1884,11 +1898,16 @@ public class VirtualDeviceManagerServiceTest { @Test public void getPersistentIdForDevice_invalidDeviceId_returnsNull() { assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_INVALID)).isNull(); - assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isNull(); assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_2)).isNull(); } @Test + public void getPersistentIdForDevice_defaultDeviceId() { + assertThat(mLocalService.getPersistentIdForDevice(DEVICE_ID_DEFAULT)).isEqualTo( + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @Test public void getPersistentIdForDevice_returnsCorrectId() { assertThat(mLocalService.getPersistentIdForDevice(VIRTUAL_DEVICE_ID_1)) .isEqualTo(mDeviceImpl.getPersistentDeviceId()); @@ -1921,7 +1940,7 @@ public class VirtualDeviceManagerServiceTest { mRunningAppsChangedCallback, params, new DisplayManagerGlobal(mIDisplayManager), - new VirtualCameraController(mContext)); + new VirtualCameraController()); mVdms.addVirtualDevice(virtualDeviceImpl); assertThat(virtualDeviceImpl.getAssociationId()).isEqualTo(mAssociationInfo.getId()); assertThat(virtualDeviceImpl.getPersistentDeviceId()) @@ -1943,6 +1962,14 @@ public class VirtualDeviceManagerServiceTest { return intent.resolveActivity(packageManager); } + private AssociationInfo createAssociationInfo(int associationId, String deviceProfile) { + return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null, + /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile, + /* associatedDevice= */ null, /* selfManaged= */ true, + /* notifyOnDeviceNearby= */ false, /* revoked= */false, /* timeApprovedMs= */0, + /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1); + } + /** Helper class to drop permissions temporarily and restore them at the end of a test. */ static final class DropShellPermissionsTemporarily implements AutoCloseable { DropShellPermissionsTemporarily() { diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java deleted file mode 100644 index dbd6c889622a..000000000000 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceRule.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.virtual; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; - -import android.app.admin.DevicePolicyManager; -import android.companion.AssociationInfo; -import android.companion.virtual.IVirtualDeviceActivityListener; -import android.companion.virtual.IVirtualDeviceSoundEffectListener; -import android.companion.virtual.VirtualDeviceParams; -import android.companion.virtual.flags.Flags; -import android.content.AttributionSource; -import android.content.Context; -import android.hardware.display.DisplayManagerGlobal; -import android.hardware.display.DisplayManagerInternal; -import android.hardware.display.IDisplayManager; -import android.net.MacAddress; -import android.os.Binder; -import android.testing.TestableContext; -import android.util.ArraySet; -import android.view.Display; -import android.view.DisplayInfo; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.android.server.LocalServices; -import com.android.server.companion.virtual.camera.VirtualCameraController; -import com.android.server.input.InputManagerInternal; -import com.android.server.sensors.SensorManagerInternal; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Objects; -import java.util.function.Consumer; -import java.util.function.Supplier; - -/** Test rule to generate instances of {@link VirtualDeviceImpl}. */ -public class VirtualDeviceRule implements TestRule { - - private static final int DEVICE_OWNER_UID = 50; - private static final int VIRTUAL_DEVICE_ID = 42; - - private final Context mContext; - private InputController mInputController; - private CameraAccessController mCameraAccessController; - private AssociationInfo mAssociationInfo; - private VirtualDeviceManagerService mVdms; - private VirtualDeviceManagerInternal mLocalService; - private VirtualDeviceLog mVirtualDeviceLog; - - // Mocks - @Mock private InputController.NativeWrapper mNativeWrapperMock; - @Mock private DisplayManagerInternal mDisplayManagerInternalMock; - @Mock private IDisplayManager mIDisplayManager; - @Mock private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; - @Mock private DevicePolicyManager mDevicePolicyManagerMock; - @Mock private InputManagerInternal mInputManagerInternalMock; - @Mock private SensorManagerInternal mSensorManagerInternalMock; - @Mock private IVirtualDeviceActivityListener mActivityListener; - @Mock private IVirtualDeviceSoundEffectListener mSoundEffectListener; - @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; - @Mock private CameraAccessController.CameraAccessBlockedCallback mCameraAccessBlockedCallback; - - // Test instance suppliers - private Supplier<VirtualCameraController> mVirtualCameraControllerSupplier; - - /** - * Create a new {@link VirtualDeviceRule} - * - * @param context The context to be used with the rule. - */ - public VirtualDeviceRule(@NonNull Context context) { - Objects.requireNonNull(context); - mContext = context; - } - - /** - * Sets a supplier that will supply an instance of {@link VirtualCameraController}. If the - * supplier returns null, a new instance will be created. - */ - public VirtualDeviceRule withVirtualCameraControllerSupplier( - Supplier<VirtualCameraController> virtualCameraControllerSupplier) { - mVirtualCameraControllerSupplier = virtualCameraControllerSupplier; - return this; - } - - @Override - public Statement apply(Statement base, Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - init(new TestableContext(mContext)); - base.evaluate(); - } - }; - } - - private void init(@NonNull TestableContext context) { - MockitoAnnotations.initMocks(this); - - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - - doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); - doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); - doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); - LocalServices.removeServiceForTest(InputManagerInternal.class); - LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); - - LocalServices.removeServiceForTest(SensorManagerInternal.class); - LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); - - final DisplayInfo displayInfo = new DisplayInfo(); - displayInfo.uniqueId = "uniqueId"; - doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); - doReturn(Display.INVALID_DISPLAY).when(mDisplayManagerInternalMock) - .getDisplayIdToMirror(anyInt()); - LocalServices.removeServiceForTest(DisplayManagerInternal.class); - LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - - context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManagerMock); - - // Allow virtual devices to be created on the looper thread for testing. - final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; - mInputController = - new InputController( - mNativeWrapperMock, - InstrumentationRegistry.getInstrumentation() - .getContext() - .getMainThreadHandler(), - context.getSystemService(WindowManager.class), - threadVerifier); - mCameraAccessController = - new CameraAccessController(context, mLocalService, mCameraAccessBlockedCallback); - - mAssociationInfo = - new AssociationInfo( - /* associationId= */ 1, - 0, - null, - null, - MacAddress.BROADCAST_ADDRESS, - "", - null, - null, - true, - false, - false, - 0, - 0, - -1); - - mVdms = new VirtualDeviceManagerService(context); - mLocalService = mVdms.getLocalServiceInstance(); - mVirtualDeviceLog = new VirtualDeviceLog(context); - } - - /** - * Create a {@link VirtualDeviceImpl} with the required mocks - * - * @param params See {@link - * android.companion.virtual.VirtualDeviceManager#createVirtualDevice(int, - * VirtualDeviceParams)} - */ - public VirtualDeviceImpl createVirtualDevice(VirtualDeviceParams params) { - VirtualCameraController virtualCameraController = mVirtualCameraControllerSupplier.get(); - if (Flags.virtualCamera()) { - if (virtualCameraController == null) { - virtualCameraController = new VirtualCameraController(mContext); - } - } - - VirtualDeviceImpl virtualDeviceImpl = - new VirtualDeviceImpl( - mContext, - mAssociationInfo, - mVdms, - mVirtualDeviceLog, - new Binder(), - new AttributionSource( - DEVICE_OWNER_UID, - "com.android.virtualdevice.test", - "virtualdevicerule"), - VIRTUAL_DEVICE_ID, - mInputController, - mCameraAccessController, - mPendingTrampolineCallback, - mActivityListener, - mSoundEffectListener, - mRunningAppsChangedCallback, - params, - new DisplayManagerGlobal(mIDisplayManager), - virtualCameraController); - mVdms.addVirtualDevice(virtualDeviceImpl); - return virtualDeviceImpl; - } -} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java new file mode 100644 index 000000000000..258302354e45 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.companion.virtual.camera; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.companion.virtual.camera.VirtualCameraCallback; +import android.companion.virtual.camera.VirtualCameraConfig; +import android.companion.virtual.camera.VirtualCameraMetadata; +import android.companion.virtual.camera.VirtualCameraStreamConfig; +import android.companion.virtualcamera.IVirtualCameraService; +import android.companion.virtualcamera.VirtualCameraConfiguration; +import android.graphics.ImageFormat; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Surface; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class VirtualCameraControllerTest { + + private static final int CAMERA_DISPLAY_NAME_RES_ID_1 = 10; + private static final int CAMERA_WIDTH_1 = 100; + private static final int CAMERA_HEIGHT_1 = 200; + private static final int CAMERA_FORMAT_1 = ImageFormat.RGB_565; + + private static final int CAMERA_DISPLAY_NAME_RES_ID_2 = 11; + private static final int CAMERA_WIDTH_2 = 400; + private static final int CAMERA_HEIGHT_2 = 600; + private static final int CAMERA_FORMAT_2 = ImageFormat.YUY2; + + @Mock + private IVirtualCameraService mVirtualCameraServiceMock; + + private VirtualCameraController mVirtualCameraController; + private final HandlerExecutor mCallbackHandler = + new HandlerExecutor(new Handler(Looper.getMainLooper())); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mVirtualCameraController = new VirtualCameraController(mVirtualCameraServiceMock); + when(mVirtualCameraServiceMock.registerCamera(any(), any())).thenReturn(true); + } + + @Test + public void registerCamera_registersCamera() throws Exception { + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + + ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = + ArgumentCaptor.forClass(VirtualCameraConfiguration.class); + verify(mVirtualCameraServiceMock).registerCamera(any(), configurationCaptor.capture()); + VirtualCameraConfiguration virtualCameraConfiguration = configurationCaptor.getValue(); + assertThat(virtualCameraConfiguration.supportedStreamConfigs.length).isEqualTo(1); + assertVirtualCameraConfiguration(virtualCameraConfiguration, CAMERA_WIDTH_1, + CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + } + + @Test + public void unregisterCamera_unregistersCamera() throws Exception { + VirtualCameraConfig config = createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1); + mVirtualCameraController.unregisterCamera(config); + + verify(mVirtualCameraServiceMock).unregisterCamera(any()); + } + + @Test + public void close_unregistersAllCameras() throws Exception { + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_DISPLAY_NAME_RES_ID_1)); + mVirtualCameraController.registerCamera(createVirtualCameraConfig( + CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_DISPLAY_NAME_RES_ID_2)); + + ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor = + ArgumentCaptor.forClass(VirtualCameraConfiguration.class); + mVirtualCameraController.close(); + verify(mVirtualCameraServiceMock, times(2)).registerCamera(any(), + configurationCaptor.capture()); + List<VirtualCameraConfiguration> virtualCameraConfigurations = + configurationCaptor.getAllValues(); + assertThat(virtualCameraConfigurations).hasSize(2); + assertVirtualCameraConfiguration(virtualCameraConfigurations.get(0), CAMERA_WIDTH_1, + CAMERA_HEIGHT_1, CAMERA_FORMAT_1); + assertVirtualCameraConfiguration(virtualCameraConfigurations.get(1), CAMERA_WIDTH_2, + CAMERA_HEIGHT_2, CAMERA_FORMAT_2); + } + + private VirtualCameraConfig createVirtualCameraConfig( + int width, int height, int format, int displayNameResId) { + return new VirtualCameraConfig.Builder() + .addStreamConfig(width, height, format) + .setDisplayNameStringRes(displayNameResId) + .setVirtualCameraCallback(mCallbackHandler, createNoOpCallback()) + .build(); + } + + private static void assertVirtualCameraConfiguration( + VirtualCameraConfiguration configuration, int width, int height, int format) { + assertThat(configuration.supportedStreamConfigs[0].width).isEqualTo(width); + assertThat(configuration.supportedStreamConfigs[0].height).isEqualTo(height); + assertThat(configuration.supportedStreamConfigs[0].pixelFormat).isEqualTo(format); + } + + private static VirtualCameraCallback createNoOpCallback() { + return new VirtualCameraCallback() { + + @Override + public void onStreamConfigured( + int streamId, + @NonNull Surface surface, + @NonNull VirtualCameraStreamConfig streamConfig) {} + + @Override + public void onProcessCaptureRequest( + int streamId, long frameId, @Nullable VirtualCameraMetadata metadata) {} + + @Override + public void onStreamClosed(int streamId) {} + }; + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index c632727fd420..9e5bea7d135b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -1680,4 +1680,47 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mHdmiControlService.isSystemAudioActivated()).isTrue(); } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_playbackActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromPlayback = + HdmiCecMessageBuilder.buildActiveSource(ADDR_PLAYBACK_1, 0x1000); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mNativeWrapper.onCecMessage(activeSourceFromPlayback); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } + + @Test + public void onAddressAllocated_startRequestActiveSourceAction_noActiveSource() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + + mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); + mNativeWrapper.clearResultMessages(); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv); + } } diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java index 2cdfbffda407..13dc12032e7d 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java @@ -57,7 +57,7 @@ import android.util.ArrayMap; import androidx.test.runner.AndroidJUnit4; import com.android.internal.app.IBatteryStats; -import com.android.net.flags.Flags; +import com.android.modules.utils.build.SdkLevel; import org.junit.After; import org.junit.Before; @@ -264,7 +264,7 @@ public class NetworkManagementServiceTest { verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID); mNMService.setDataSaverModeEnabled(true); - if (Flags.setDataSaverViaCm()) { + if (SdkLevel.isAtLeastV()) { verify(mCm).setDataSaverEnabled(true); } else { verify(mNetdService).bandwidthEnableDataSaver(true); @@ -284,7 +284,7 @@ public class NetworkManagementServiceTest { mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false); verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID); mNMService.setDataSaverModeEnabled(false); - if (Flags.setDataSaverViaCm()) { + if (SdkLevel.isAtLeastV()) { verify(mCm).setDataSaverEnabled(false); } else { verify(mNetdService).bandwidthEnableDataSaver(false); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index c684a7bbf884..57b12251c207 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -67,6 +67,7 @@ public class UserManagerServiceUserPropertiesTest { .setCrossProfileIntentResolutionStrategy(0) .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) + .setAuthAlwaysRequiredToDisableQuietMode(false) .setDeleteAppWithParent(false) .setAlwaysVisible(false) .build(); @@ -80,6 +81,7 @@ public class UserManagerServiceUserPropertiesTest { actualProps.setCrossProfileIntentResolutionStrategy(1); actualProps.setMediaSharedWithParent(true); actualProps.setCredentialShareableWithParent(false); + actualProps.setAuthAlwaysRequiredToDisableQuietMode(true); actualProps.setDeleteAppWithParent(true); actualProps.setAlwaysVisible(true); @@ -123,6 +125,7 @@ public class UserManagerServiceUserPropertiesTest { .setInheritDevicePolicy(1732) .setMediaSharedWithParent(true) .setDeleteAppWithParent(true) + .setAuthAlwaysRequiredToDisableQuietMode(false) .setAlwaysVisible(true) .build(); final UserProperties orig = new UserProperties(defaultProps); @@ -131,6 +134,7 @@ public class UserManagerServiceUserPropertiesTest { orig.setShowInSettings(1437); orig.setInheritDevicePolicy(9456); orig.setDeleteAppWithParent(false); + orig.setAuthAlwaysRequiredToDisableQuietMode(true); orig.setAlwaysVisible(false); // Test every permission level. (Currently, it's linear so it's easy.) @@ -182,6 +186,8 @@ public class UserManagerServiceUserPropertiesTest { hasManagePermission); assertEqualGetterOrThrows(orig::getUseParentsContacts, copy::getUseParentsContacts, hasManagePermission); + assertEqualGetterOrThrows(orig::isAuthAlwaysRequiredToDisableQuietMode, + copy::isAuthAlwaysRequiredToDisableQuietMode, hasManagePermission); // Items requiring hasQueryPermission - put them here using hasQueryPermission. @@ -242,6 +248,8 @@ public class UserManagerServiceUserPropertiesTest { .isEqualTo(actual.isMediaSharedWithParent()); assertThat(expected.isCredentialShareableWithParent()) .isEqualTo(actual.isCredentialShareableWithParent()); + assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode()) + .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode()); assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent()); assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible()); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index 20270a817831..48eb5c64f3d1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -89,6 +89,7 @@ public class UserManagerServiceUserTypeTest { .setCrossProfileIntentResolutionStrategy(1) .setMediaSharedWithParent(true) .setCredentialShareableWithParent(false) + .setAuthAlwaysRequiredToDisableQuietMode(true) .setShowInSettings(900) .setHideInSettingsInQuietMode(true) .setInheritDevicePolicy(340) @@ -160,6 +161,8 @@ public class UserManagerServiceUserTypeTest { .getCrossProfileIntentResolutionStrategy()); assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent()); assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent()); + assertTrue(type.getDefaultUserPropertiesReference() + .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(340, type.getDefaultUserPropertiesReference() @@ -306,6 +309,7 @@ public class UserManagerServiceUserTypeTest { .setCrossProfileIntentResolutionStrategy(1) .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) + .setAuthAlwaysRequiredToDisableQuietMode(false) .setShowInSettings(20) .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(21) @@ -347,6 +351,8 @@ public class UserManagerServiceUserTypeTest { assertFalse(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent()); assertTrue(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); + assertFalse(aospType.getDefaultUserPropertiesReference() + .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(21, aospType.getDefaultUserPropertiesReference() @@ -394,6 +400,8 @@ public class UserManagerServiceUserTypeTest { assertTrue(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent()); assertFalse(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); + assertTrue(aospType.getDefaultUserPropertiesReference() + .isAuthAlwaysRequiredToDisableQuietMode()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(450, aospType.getDefaultUserPropertiesReference() diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 775d42a4df5f..2b6d8eda6bb0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -331,6 +331,9 @@ public final class UserManagerTest { .isEqualTo(privateProfileUserProperties.isMediaSharedWithParent()); assertThat(typeProps.isCredentialShareableWithParent()) .isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent()); + assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode()) + .isEqualTo(privateProfileUserProperties + .isAuthAlwaysRequiredToDisableQuietMode()); assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent); // Verify private profile parent diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index d09aa89179b8..37485275dac7 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -44,6 +44,7 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; +import android.os.WorkDuration; import android.util.Log; import com.android.server.FgThread; @@ -89,6 +90,11 @@ public class HintManagerServiceTest { private static final long[] DURATIONS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_ZERO = new long[] {}; private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; + private static final WorkDuration[] WORK_DURATIONS_THREE = new WorkDuration[] { + new WorkDuration(1L, 11L, 8L, 4L, 1L), + new WorkDuration(2L, 13L, 8L, 6L, 2L), + new WorkDuration(3L, 333333333L, 8L, 333333333L, 3L), + }; @Mock private Context mContext; @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @@ -593,4 +599,55 @@ public class HintManagerServiceTest { } a.close(); } + + @Test + public void testReportActualWorkDuration2() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + a.updateTargetWorkDuration(100L); + a.reportActualWorkDuration2(WORK_DURATIONS_THREE); + verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), + eq(WORK_DURATIONS_THREE)); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration2(new WorkDuration[] {}); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(0L, 11L, 8L, 4L, 1L)}); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 0L, 8L, 4L, 1L)}); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration2(new WorkDuration[] {new WorkDuration(1L, 11L, 0L, 4L, 1L)}); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration2( + new WorkDuration[] {new WorkDuration(1L, 11L, 8L, -1L, 1L)}); + }); + + reset(mNativeWrapperMock); + // Set session to background, then the duration would not be updated. + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + + // Using CountDownLatch to ensure above onUidStateChanged() job was digested. + final CountDownLatch latch = new CountDownLatch(1); + FgThread.getHandler().post(() -> { + latch.countDown(); + }); + latch.await(); + + assertFalse(service.mUidObserver.isUidForeground(a.mUid)); + a.reportActualWorkDuration2(WORK_DURATIONS_THREE); + verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java index 4406d831dd93..ea113957dddd 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java @@ -426,7 +426,7 @@ public class NotificationListenersTest extends UiServiceTestCase { } @Test - public void testOnPackageChanged_removingDisallowedPackage() { + public void testOnPackageChanged_removingPackage_removeFromDisallowed() { NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); VersionedPackage a1 = new VersionedPackage("pkg1", 243); NotificationListenerFilter nlf2 = @@ -440,6 +440,25 @@ public class NotificationListenersTest extends UiServiceTestCase { assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)) .getDisallowedPackages()).isEmpty(); + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) + .getDisallowedPackages()).isEmpty(); + } + + @Test + public void testOnPackageChanged_notRemovingPackage_staysInDisallowed() { + NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>()); + VersionedPackage a1 = new VersionedPackage("pkg1", 243); + NotificationListenerFilter nlf2 = + new NotificationListenerFilter(4, new ArraySet<>(new VersionedPackage[] {a1})); + mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf); + mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2); + + String[] pkgs = new String[] {"pkg1"}; + int[] uids = new int[] {243}; + mListeners.onPackagesChanged(false, pkgs, uids); + + assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)) + .getDisallowedPackages()).contains(a1); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 3803244c7012..7fb8b30dea06 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3239,7 +3239,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel( anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn( - new NotificationChannel("foo", "foo", IMPORTANCE_HIGH)); + new NotificationChannel("foo", "foo", IMPORTANCE_HIGH)); Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo"); mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0, @@ -9927,6 +9927,174 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testRestoreConversationChannel_deleted() throws Exception { + // Create parent channel + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + final NotificationChannel originalChannel = new NotificationChannel("id", "name", + IMPORTANCE_DEFAULT); + NotificationChannel parentChannel = parcelAndUnparcel(originalChannel, + NotificationChannel.CREATOR); + assertEquals(originalChannel, parentChannel); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(parentChannel))); + + //Create deleted conversation channel + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID); + final NotificationChannel conversationChannel = + mBinderService.getConversationNotificationChannel( + PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID); + conversationChannel.setDeleted(true); + + //Create notification record + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId(VALID_CONVO_SHORTCUT_ID); + nb.setChannelId(originalChannel.getId()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel); + assertThat(nr.getChannel()).isEqualTo(originalChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Verify that the channel was changed to the conversation channel and restored + assertThat(mService.getNotificationRecord(nr.getKey()).isConversation()).isTrue(); + assertThat(mService.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo( + conversationChannel); + assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().isDeleted()).isFalse(); + assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().getDeletedTimeMs()) + .isEqualTo(-1); + } + + @Test + public void testDoNotRestoreParentChannel_deleted() throws Exception { + // Create parent channel and set as deleted + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + final NotificationChannel originalChannel = new NotificationChannel("id", "name", + IMPORTANCE_DEFAULT); + NotificationChannel parentChannel = parcelAndUnparcel(originalChannel, + NotificationChannel.CREATOR); + assertEquals(originalChannel, parentChannel); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(parentChannel))); + parentChannel.setDeleted(true); + + //Create notification record + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId(VALID_CONVO_SHORTCUT_ID); + nb.setChannelId(originalChannel.getId()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel); + assertThat(nr.getChannel()).isEqualTo(originalChannel); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Verify that the channel was not restored and the notification was not posted + assertThat(mService.mChannelToastsSent).contains(mUid); + assertThat(mService.getNotificationRecord(nr.getKey())).isNull(); + assertThat(parentChannel.isDeleted()).isTrue(); + } + + @Test + public void testEnqueueToConversationChannel_notDeleted_doesNotRestore() throws Exception { + TestableNotificationManagerService service = spy(mService); + PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper); + service.setPreferencesHelper(preferencesHelper); + // Create parent channel + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + final NotificationChannel originalChannel = new NotificationChannel("id", "name", + IMPORTANCE_DEFAULT); + NotificationChannel parentChannel = parcelAndUnparcel(originalChannel, + NotificationChannel.CREATOR); + assertEquals(originalChannel, parentChannel); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(parentChannel))); + + //Create conversation channel + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID); + final NotificationChannel conversationChannel = + mBinderService.getConversationNotificationChannel( + PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID); + + //Create notification record + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId(VALID_CONVO_SHORTCUT_ID); + nb.setChannelId(originalChannel.getId()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel); + assertThat(nr.getChannel()).isEqualTo(originalChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Verify that the channel was changed to the conversation channel and not restored + assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isTrue(); + assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo( + conversationChannel); + verify(service, never()).handleSavePolicyFile(); + verify(preferencesHelper, never()).createNotificationChannel(anyString(), + anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean()); + } + + @Test + public void testEnqueueToParentChannel_notDeleted_doesNotRestore() throws Exception { + TestableNotificationManagerService service = spy(mService); + PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper); + service.setPreferencesHelper(preferencesHelper); + // Create parent channel + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + final NotificationChannel originalChannel = new NotificationChannel("id", "name", + IMPORTANCE_DEFAULT); + NotificationChannel parentChannel = parcelAndUnparcel(originalChannel, + NotificationChannel.CREATOR); + assertEquals(originalChannel, parentChannel); + mBinderService.createNotificationChannels(PKG, + new ParceledListSlice(Arrays.asList(parentChannel))); + + //Create deleted conversation channel + mBinderService.createConversationNotificationChannelForPackage( + PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID); + final NotificationChannel conversationChannel = + mBinderService.getConversationNotificationChannel( + PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID); + + //Create notification record without a shortcutId + Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */, + null /* groupKey */, false /* isSummary */); + nb.setShortcutId(null); + nb.setChannelId(originalChannel.getId()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel); + assertThat(nr.getChannel()).isEqualTo(originalChannel); + + mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + // Verify that the channel is the parent channel and no channel was restored + //assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isFalse(); + assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo( + parentChannel); + verify(service, never()).handleSavePolicyFile(); + verify(preferencesHelper, never()).createNotificationChannel(anyString(), + anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean()); + } + + @Test public void testGetConversationsForPackage_hasShortcut() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); ArrayList<ConversationChannelWrapper> convos = new ArrayList<>(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java new file mode 100644 index 000000000000..8dcf89bb8438 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.service.notification.ZenDeviceEffects; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ZenDeviceEffectsTest extends UiServiceTestCase { + + @Test + public void builder() { + ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false) + .setShouldDisableTiltToWake(true) + .setShouldMaximizeDoze(true) + .setShouldUseNightMode(false) + .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true) + .build(); + + assertThat(deviceEffects.shouldDimWallpaper()).isTrue(); + assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse(); + assertThat(deviceEffects.shouldDisableTapToWake()).isFalse(); + assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue(); + assertThat(deviceEffects.shouldDisableTouch()).isFalse(); + assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse(); + assertThat(deviceEffects.shouldMaximizeDoze()).isTrue(); + assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse(); + assertThat(deviceEffects.shouldUseNightMode()).isFalse(); + assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue(); + } + + @Test + public void builder_fromInstance() { + ZenDeviceEffects original = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTiltToWake(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(false) + .build(); + + assertThat(modified.shouldDimWallpaper()).isTrue(); // from original + assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original + assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated + assertThat(modified.shouldUseNightMode()).isFalse(); // updated + assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original + } + + @Test + public void writeToParcel_parcelsAndUnparcels() { + ZenDeviceEffects source = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisableTouch(true) + .setShouldMinimizeRadioUsage(true) + .setShouldUseNightMode(true) + .setShouldSuppressAmbientDisplay(true) + .build(); + + Parcel parcel = Parcel.obtain(); + ZenDeviceEffects copy; + try { + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + + assertThat(copy.shouldDimWallpaper()).isTrue(); + assertThat(copy.shouldDisableTouch()).isTrue(); + assertThat(copy.shouldMinimizeRadioUsage()).isTrue(); + assertThat(copy.shouldUseNightMode()).isTrue(); + assertThat(copy.shouldSuppressAmbientDisplay()).isTrue(); + assertThat(copy.shouldDisplayGrayscale()).isFalse(); + } +} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index 7a2bb5a90846..f0803418376f 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -75,8 +75,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; -import android.util.ArraySet; -import android.view.Display; import androidx.test.InstrumentationRegistry; @@ -103,7 +101,7 @@ public class VibrationSettingsTest { public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private static final int UID = 1; - private static final int VIRTUAL_DISPLAY_ID = 1; + private static final int VIRTUAL_DEVICE_ID = 1; private static final String SYSUI_PACKAGE_NAME = "sysui"; private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() @@ -137,9 +135,6 @@ public class VibrationSettingsTest { private VibrationSettings mVibrationSettings; private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private BroadcastReceiver mRegisteredBatteryBroadcastReceiver; - private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener; - private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener - mRegisteredAppsOnVirtualDeviceListener; @Before public void setUp() throws Exception { @@ -155,14 +150,6 @@ public class VibrationSettingsTest { }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); when(mPackageManagerInternalMock.getSystemUiServiceComponent()) .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, "")); - doAnswer(invocation -> { - mRegisteredVirtualDisplayListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any()); - doAnswer(invocation -> { - mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any()); removeServicesForTest(); addServicesForTest(); @@ -654,62 +641,20 @@ public class VibrationSettingsTest { } @Test - public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() { - // Vibrations from the primary display is never ignored regardless of the creation and - // removal of virtual displays and of the changes of apps running on virtual displays. - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); - } - - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); + public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() { + // Vibrations from the primary device is never ignored. for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); - } - - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY); + assertVibrationNotIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT); } } @Test - public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() { - // Ignore the vibration when the coming display id represents a virtual display. - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - + public void shouldIgnoreVibrationFromVirtualDevices_virtualDevice_alwaysIgnored() { + // Ignore the vibration when the coming device id represents a virtual device. for (int usage : ALL_USAGES) { - assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID, + assertVibrationIgnoredForUsageAndDevice(usage, VIRTUAL_DEVICE_ID, Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE); } - - // Stop ignoring when the virtual display is removed. - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID); - } - } - - - @Test - public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() { - // Ignore when the passed-in display id is invalid and the calling uid is on a virtual - // display. - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - for (int usage : ALL_USAGES) { - assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY, - Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE); - } - - // Stop ignoring when the app is no longer on virtual display. - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - for (int usage : ALL_USAGES) { - assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY); - } - } @Test @@ -932,13 +877,13 @@ public class VibrationSettingsTest { private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage, Vibration.Status expectedStatus) { - assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus); + assertVibrationIgnoredForUsageAndDevice(usage, Context.DEVICE_ID_DEFAULT, expectedStatus); } - private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, - int displayId, Vibration.Status expectedStatus) { + private void assertVibrationIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage, + int deviceId, Vibration.Status expectedStatus) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( - VibrationAttributes.createForUsage(usage), UID, displayId, null, null); + VibrationAttributes.createForUsage(usage), UID, deviceId, null, null); assertEquals(errorMessageForUsage(usage), expectedStatus, mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -946,7 +891,7 @@ public class VibrationSettingsTest { private void assertVibrationIgnoredForAttributes(VibrationAttributes attrs, Vibration.Status expectedStatus) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, - Display.DEFAULT_DISPLAY, null, null); + Context.DEVICE_ID_DEFAULT, null, null); assertEquals(errorMessageForAttributes(attrs), expectedStatus, mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -957,27 +902,27 @@ public class VibrationSettingsTest { private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage, @VibrationAttributes.Flag int flags) { - assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, Display.DEFAULT_DISPLAY, flags); + assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, Context.DEVICE_ID_DEFAULT, flags); } - private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage, - int displayId) { - assertVibrationNotIgnoredForUsageAndFlagsAndDisplay(usage, displayId, /* flags= */ 0); + private void assertVibrationNotIgnoredForUsageAndDevice(@VibrationAttributes.Usage int usage, + int deviceId) { + assertVibrationNotIgnoredForUsageAndFlagsAndDevice(usage, deviceId, /* flags= */ 0); } - private void assertVibrationNotIgnoredForUsageAndFlagsAndDisplay( - @VibrationAttributes.Usage int usage, int displayId, + private void assertVibrationNotIgnoredForUsageAndFlagsAndDevice( + @VibrationAttributes.Usage int usage, int deviceId, @VibrationAttributes.Flag int flags) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo( new VibrationAttributes.Builder().setUsage(usage).setFlags(flags).build(), UID, - displayId, null, null); + deviceId, null, null); assertNull(errorMessageForUsage(usage), mVibrationSettings.shouldIgnoreVibration(callerInfo)); } private void assertVibrationNotIgnoredForAttributes(VibrationAttributes attrs) { Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(attrs, UID, - Display.DEFAULT_DISPLAY, null, null); + Context.DEVICE_ID_DEFAULT, null, null); assertNull(errorMessageForAttributes(attrs), mVibrationSettings.shouldIgnoreVibration(callerInfo)); } @@ -1032,7 +977,7 @@ public class VibrationSettingsTest { private Vibration.CallerInfo createCallerInfo(int uid, String opPkg, @VibrationAttributes.Usage int usage) { VibrationAttributes attrs = VibrationAttributes.createForUsage(usage); - return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DISPLAY_ID, opPkg, null); + return new Vibration.CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null); } private void setBatteryReceiverRegistrationResult(Intent result) { 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 085241ff971a..b0aef47d0900 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -88,7 +88,7 @@ public class VibrationThreadTest { private static final int TEST_TIMEOUT_MILLIS = 900; private static final int UID = Process.ROOT_UID; - private static final int DISPLAY_ID = 10; + private static final int DEVICE_ID = 10; private static final int VIBRATOR_ID = 1; private static final String PACKAGE_NAME = "package"; private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build(); @@ -250,7 +250,7 @@ public class VibrationThreadTest { Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo( Vibration.Status.CANCELLED_SUPERSEDED, new Vibration.CallerInfo( VibrationAttributes.createForUsage(VibrationAttributes.USAGE_ALARM), /* uid= */ - 1, /* displayId= */ -1, /* opPkg= */ null, /* reason= */ null)); + 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null)); mVibrationConductor.notifyCancelled( cancelVibrationInfo, /* immediate= */ false); @@ -1641,7 +1641,7 @@ public class VibrationThreadTest { private HalVibration createVibration(CombinedVibration effect) { return new HalVibration(mVibrationToken, effect, - new Vibration.CallerInfo(ATTRS, UID, DISPLAY_ID, PACKAGE_NAME, "reason")); + new Vibration.CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason")); } private SparseArray<VibratorController> createVibratorControllers() { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 3dfaed69dea6..3fce9e7a83ef 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -84,10 +84,8 @@ import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; -import android.util.ArraySet; import android.util.SparseArray; import android.util.SparseBooleanArray; -import android.view.Display; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.flags.Flags; @@ -129,7 +127,7 @@ public class VibratorManagerServiceTest { // be cancelled in the body of the individual test. private static final int CLEANUP_TIMEOUT_MILLIS = 100; private static final int UID = Process.ROOT_UID; - private static final int VIRTUAL_DISPLAY_ID = 1; + private static final int VIRTUAL_DEVICE_ID = 1; private static final String PACKAGE_NAME = "package"; private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() @@ -190,9 +188,6 @@ public class VibratorManagerServiceTest { private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private VibratorManagerService.ExternalVibratorService mExternalVibratorService; private VibrationConfig mVibrationConfig; - private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener; - private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener - mRegisteredAppsOnVirtualDeviceListener; private InputManagerGlobal.TestSession mInputManagerGlobalSession; private InputManager mInputManager; @@ -223,14 +218,6 @@ public class VibratorManagerServiceTest { mRegisteredPowerModeListener = invocation.getArgument(0); return null; }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); - doAnswer(invocation -> { - mRegisteredVirtualDisplayListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any()); - doAnswer(invocation -> { - mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0); - return null; - }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any()); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); @@ -273,6 +260,7 @@ public class VibratorManagerServiceTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(PowerManagerInternal.class); + LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); // Ignore potential exceptions about the looper having never dispatched any messages. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); if (mInputManagerGlobalSession != null) { @@ -1510,65 +1498,33 @@ public class VibratorManagerServiceTest { } @Test - public void vibrate_withVirtualDisplayChange_ignoreVibrationFromVirtualDisplay() - throws Exception { + public void vibrate_ignoreVibrationFromVirtualDevice() throws Exception { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); - mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID); - vibrateWithDisplay(service, - VIRTUAL_DISPLAY_ID, + vibrateWithDevice(service, + VIRTUAL_DEVICE_ID, CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) .combine(), HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback ignored when it's from a virtual display. + // Haptic feedback ignored when it's from a virtual device. assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50)); - mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID); - vibrateWithDisplay(service, - VIRTUAL_DISPLAY_ID, + vibrateWithDevice(service, + Context.DEVICE_ID_DEFAULT, CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) .combine(), HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback played normally when the virtual display is removed. + // Haptic feedback played normally when it's from the default device. assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); cancelVibrate(service); // Clean up long-ish effect. } @Test - public void vibrate_withAppsOnVirtualDisplayChange_ignoreVibrationFromVirtualDisplay() - throws Exception { - mockVibrators(1); - VibratorManagerService service = createSystemReadyService(); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); - vibrateWithDisplay(service, - Display.INVALID_DISPLAY, - CombinedVibration.startParallel() - .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) - .combine(), - HAPTIC_FEEDBACK_ATTRS); - - // Haptic feedback ignored when it's from an app running virtual display. - assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50)); - - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>()); - vibrateWithDisplay(service, - Display.INVALID_DISPLAY, - CombinedVibration.startParallel() - .addVibrator(1, VibrationEffect.createOneShot(1000, 100)) - .combine(), - HAPTIC_FEEDBACK_ATTRS); - // Haptic feedback played normally when the same app no long runs on a virtual display. - assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); - cancelVibrate(service); // Clean up long-ish effect. - } - - @Test public void vibrate_prebakedAndComposedVibrationsWithFallbacks_playsFallbackOnlyForPredefined() throws Exception { mockVibrators(1); @@ -1685,7 +1641,7 @@ public class VibratorManagerServiceTest { } @Test - public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception { + public void onExternalVibration_ignoreVibrationFromVirtualDevices() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); createSystemReadyService(); @@ -1697,8 +1653,8 @@ public class VibratorManagerServiceTest { int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale); - mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged( - new ArraySet<>(Arrays.asList(UID))); + when(mVirtualDeviceManagerInternalMock.isAppRunningOnAnyVirtualDevice(UID)) + .thenReturn(true); scale = mExternalVibratorService.onExternalVibrationStart(externalVibration); assertEquals(IExternalVibratorService.SCALE_MUTE, scale); } @@ -2396,7 +2352,7 @@ public class VibratorManagerServiceTest { private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service, int constant, boolean always) throws InterruptedException { HalVibration vib = - service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + service.performHapticFeedbackInternal(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, constant, always, "some reason", service); if (vib != null) { vib.waitForEnd(); @@ -2414,7 +2370,7 @@ public class VibratorManagerServiceTest { private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service, CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException { HalVibration vib = - service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, + service.vibrateWithPermissionCheck(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, effect, attrs, "some reason", service); if (vib != null) { vib.waitForEnd(); @@ -2430,12 +2386,12 @@ public class VibratorManagerServiceTest { private void vibrate(VibratorManagerService service, CombinedVibration effect, VibrationAttributes attrs) { - vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs); + vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs); } - private void vibrateWithDisplay(VibratorManagerService service, int displayId, + private void vibrateWithDevice(VibratorManagerService service, int deviceId, CombinedVibration effect, VibrationAttributes attrs) { - service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service); + service.vibrate(UID, deviceId, PACKAGE_NAME, effect, attrs, "some reason", service); } private boolean waitUntil(Predicate<VibratorManagerService> predicate, 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 eab8757b7331..912e1d3df945 100644 --- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java @@ -16,15 +16,19 @@ package com.android.server.policy; +import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS; import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS; import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY; +import android.app.ActivityManager.RecentTaskInfo; import android.content.ComponentName; +import android.os.RemoteException; import android.provider.Settings; import org.junit.Test; @@ -120,6 +124,46 @@ public class StemKeyGestureTests extends ShortcutKeyTestBase { mPhoneWindowManager.assertStatusBarStartAssist(); } + @Test + public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent() + throws RemoteException { + overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); + int referenceId = 666; + recentTaskInfo.persistentId = referenceId; + doReturn(recentTaskInfo).when( + mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground(); + + sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertOpenAllAppView(); + mPhoneWindowManager.assertSwitchToRecent(referenceId); + } + + @Test + public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException { + overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS); + setUpPhoneWindowManager(/* supportSettingsUpdate= */ true); + mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false); + mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false); + mPhoneWindowManager.overrideIsUserSetupComplete(true); + RecentTaskInfo recentTaskInfo = new RecentTaskInfo(); + int referenceId = 666; + recentTaskInfo.persistentId = referenceId; + doReturn(recentTaskInfo).when( + mPhoneWindowManager.mActivityTaskManagerInternal).getMostRecentTaskFromBackground(); + + sendKey(KEYCODE_STEM_PRIMARY); + sendKey(KEYCODE_STEM_PRIMARY); + + mPhoneWindowManager.assertNotOpenAllAppView(); + mPhoneWindowManager.assertSwitchToRecent(referenceId); + } private void overrideBehavior(String key, int expectedBehavior) { Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior); diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index e26260a6836c..314cd04695ba 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -57,6 +57,7 @@ import static org.mockito.Mockito.withSettings; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; +import android.app.IActivityManager; import android.app.NotificationManager; import android.app.SearchManager; import android.content.ComponentName; @@ -126,7 +127,8 @@ class TestPhoneWindowManager { @Mock private WindowManagerInternal mWindowManagerInternal; @Mock private ActivityManagerInternal mActivityManagerInternal; - @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal; + @Mock ActivityTaskManagerInternal mActivityTaskManagerInternal; + @Mock IActivityManager mActivityManagerService; @Mock private InputManagerInternal mInputManagerInternal; @Mock private InputManager mInputManager; @Mock private SensorPrivacyManager mSensorPrivacyManager; @@ -181,6 +183,10 @@ class TestPhoneWindowManager { KeyguardServiceDelegate getKeyguardServiceDelegate() { return mKeyguardServiceDelegate; } + + IActivityManager getActivityManagerService() { + return mActivityManagerService; + } } TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) { @@ -347,6 +353,10 @@ class TestPhoneWindowManager { mPhoneWindowManager.mShortPressOnPowerBehavior = behavior; } + void overrideShouldEarlyShortPressOnStemPrimary(boolean shouldEarlyShortPress) { + mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress; + } + // Override assist perform function. void overrideLongPressOnPower(int behavior) { mPhoneWindowManager.mLongPressOnPowerBehavior = behavior; @@ -667,4 +677,11 @@ class TestPhoneWindowManager { vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey}, expectedModifierState), description(errorMsg)); } + + void assertSwitchToRecent(int persistentId) throws RemoteException { + mTestLooper.dispatchAll(); + verify(mActivityManagerService, + timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId), + isNull()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 0c996e0155fd..1776ba556b72 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -195,6 +195,8 @@ public class ActivityRecordTests extends WindowTestsBase { setBooted(mAtm); // Because the booted state is set, avoid starting real home if there is no task. doReturn(false).when(mRootWindowContainer).resumeHomeActivity(any(), anyString(), any()); + // Do not execute the transaction, because we can't verify the parameter after it recycles. + doNothing().when(mClientLifecycleManager).scheduleTransaction(any()); } private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() { @@ -262,7 +264,7 @@ public class ActivityRecordTests extends WindowTestsBase { pauseFound.value = true; } return null; - }).when(activity.app.getThread()).scheduleTransaction(any()); + }).when(mClientLifecycleManager).scheduleTransaction(any()); activity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); @@ -477,7 +479,7 @@ public class ActivityRecordTests extends WindowTestsBase { .build(); final Task task = activity.getTask(); activity.setState(DESTROYED, "Testing"); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); final Configuration newConfig = new Configuration(task.getConfiguration()); newConfig.orientation = newConfig.orientation == ORIENTATION_PORTRAIT @@ -487,7 +489,7 @@ public class ActivityRecordTests extends WindowTestsBase { ensureActivityConfiguration(activity); - verify(mAtm.getLifecycleManager(), never()) + verify(mClientLifecycleManager, never()) .scheduleTransaction(any(), isA(ActivityConfigurationChangeItem.class)); } @@ -500,7 +502,7 @@ public class ActivityRecordTests extends WindowTestsBase { // test properly. activity.finishRelaunching(); // Clear out any calls to scheduleTransaction from launching the activity. - reset(mAtm.getLifecycleManager()); + reset(mClientLifecycleManager); final Task task = activity.getTask(); activity.setState(RESUMED, "Testing"); @@ -517,7 +519,7 @@ public class ActivityRecordTests extends WindowTestsBase { // The configuration change is still sent to the activity, even if it doesn't relaunch. final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction( + verify(mClientLifecycleManager).scheduleTransaction( eq(activity.app.getThread()), eq(expected)); } @@ -558,19 +560,7 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), activity.getConfiguration())); - clearInvocations(mAtm.getLifecycleManager()); - final Configuration newConfig = new Configuration(activity.getConfiguration()); - final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); - final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); - if (newConfig.orientation == ORIENTATION_PORTRAIT) { - newConfig.orientation = ORIENTATION_LANDSCAPE; - newConfig.screenWidthDp = longSide; - newConfig.screenHeightDp = shortSide; - } else { - newConfig.orientation = ORIENTATION_PORTRAIT; - newConfig.screenWidthDp = shortSide; - newConfig.screenHeightDp = longSide; - } + clearInvocations(mClientLifecycleManager); // Mimic the behavior that display doesn't handle app's requested orientation. final DisplayContent dc = activity.getTask().getDisplayContent(); @@ -578,12 +568,15 @@ public class ActivityRecordTests extends WindowTestsBase { doReturn(false).when(dc).handlesOrientationChangeFromDescendant(anyInt()); final int requestedOrientation; - switch (newConfig.orientation) { - case ORIENTATION_LANDSCAPE: + final int expectedOrientation; + switch (activity.getConfiguration().orientation) { + case ORIENTATION_PORTRAIT: requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE; + expectedOrientation = ORIENTATION_LANDSCAPE; break; - case ORIENTATION_PORTRAIT: + case ORIENTATION_LANDSCAPE: requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + expectedOrientation = ORIENTATION_PORTRAIT; break; default: throw new IllegalStateException("Orientation in new config should be either" @@ -595,11 +588,11 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation(requestedOrientation); + final Configuration currentConfig = activity.getConfiguration(); + assertEquals(expectedOrientation, currentConfig.orientation); final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()), - eq(expected)); - + ActivityConfigurationChangeItem.obtain(activity.token, currentConfig); + verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); verify(displayRotation).onSetRequestedOrientation(); } @@ -788,7 +781,7 @@ public class ActivityRecordTests extends WindowTestsBase { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); try { - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); doReturn(false).when(stack).isTranslucent(any()); assertTrue(task.shouldBeVisible(null /* starting */)); @@ -796,7 +789,10 @@ public class ActivityRecordTests extends WindowTestsBase { activity.getConfiguration())); final Configuration newConfig = new Configuration(activity.getConfiguration()); - final int shortSide = Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); + final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp + // To avoid the case where it is always portrait because of width == height. + ? newConfig.screenWidthDp - 1 + : Math.min(newConfig.screenWidthDp, newConfig.screenHeightDp); final int longSide = Math.max(newConfig.screenWidthDp, newConfig.screenHeightDp); if (newConfig.orientation == ORIENTATION_PORTRAIT) { newConfig.orientation = ORIENTATION_LANDSCAPE; @@ -811,12 +807,12 @@ public class ActivityRecordTests extends WindowTestsBase { task.onConfigurationChanged(newConfig); activity.ensureActivityConfiguration(0 /* globalChanges */, - false /* preserveWindow */, true /* ignoreStopState */); + false /* preserveWindow */, true /* ignoreVisibility */); final ActivityConfigurationChangeItem expected = - ActivityConfigurationChangeItem.obtain(activity.token, newConfig); - verify(mAtm.getLifecycleManager()).scheduleTransaction( - eq(activity.app.getThread()), eq(expected)); + ActivityConfigurationChangeItem.obtain(activity.token, + activity.getConfiguration()); + verify(mClientLifecycleManager).scheduleTransaction(activity.app.getThread(), expected); } finally { stack.getDisplayArea().removeChild(stack); } @@ -1259,12 +1255,12 @@ public class ActivityRecordTests extends WindowTestsBase { targetActivity.resultTo = sourceActivity; targetActivity.setForceSendResultForMediaProjection(); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); try { - verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction( + verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction( any(ClientTransaction.class)); } catch (RemoteException ignored) { } @@ -1283,7 +1279,7 @@ public class ActivityRecordTests extends WindowTestsBase { targetActivity.setState(RESUMED, "test"); targetActivity.resultTo = resultToActivity; - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); waitUntilHandlersIdle(); @@ -1786,10 +1782,10 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); final WindowProcessController wpc = activity.app; setup.accept(activity); - clearInvocations(mAtm.getLifecycleManager()); + clearInvocations(mClientLifecycleManager); activity.getTask().removeImmediately("test"); try { - verify(mAtm.getLifecycleManager()).scheduleTransaction(any(), + verify(mClientLifecycleManager).scheduleTransaction(any(), isA(DestroyActivityItem.class)); } catch (RemoteException ignored) { } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index e7ebd7db1023..3c027ffa5dac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -39,9 +39,10 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.never; @@ -51,7 +52,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.IApplicationThread; import android.app.PictureInPictureParams; -import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; import android.app.servertransaction.EnterPipRequestedItem; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -90,9 +91,6 @@ import java.util.function.Consumer; @RunWith(WindowTestRunner.class) public class ActivityTaskManagerServiceTests extends WindowTestsBase { - private final ArgumentCaptor<ClientTransaction> mClientTransactionCaptor = - ArgumentCaptor.forClass(ClientTransaction.class); - private static final String DEFAULT_PACKAGE_NAME = "my.application.package"; private static final int DEFAULT_USER_ID = 100; @@ -123,53 +121,42 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { final ClientLifecycleManager mockLifecycleManager = mock(ClientLifecycleManager.class); doReturn(mockLifecycleManager).when(mAtm).getLifecycleManager(); doReturn(true).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(mockLifecycleManager).scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); + final ArgumentCaptor<ClientTransactionItem> clientTransactionItemCaptor = + ArgumentCaptor.forClass(ClientTransactionItem.class); + verify(mockLifecycleManager).scheduleTransaction(any(), + clientTransactionItemCaptor.capture()); + final ClientTransactionItem transactionItem = clientTransactionItemCaptor.getValue(); // Check that only an enter pip request item callback was scheduled. - assertEquals(1, transaction.getCallbacks().size()); - assertTrue(transaction.getCallbacks().get(0) instanceof EnterPipRequestedItem); - // Check the activity lifecycle state remains unchanged. - assertNull(transaction.getLifecycleStateRequest()); + assertTrue(transactionItem instanceof EnterPipRequestedItem); } @Test public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); doReturn(false).when(activity).inPinnedWindowingMode(); doReturn(false).when(activity).checkEnterPictureInPictureState(anyString(), anyBoolean()); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(lifecycleManager, atLeast(0)) - .scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); - // Check that none are enter pip request items. - transaction.getCallbacks().forEach(clientTransactionItem -> { - assertFalse(clientTransactionItem instanceof EnterPipRequestedItem); - }); + verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); } @Test public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException { final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity(); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); doReturn(true).when(activity).inPinnedWindowingMode(); + clearInvocations(mClientLifecycleManager); mAtm.mActivityClientController.requestPictureInPictureMode(activity); - verify(lifecycleManager, atLeast(0)) - .scheduleTransaction(mClientTransactionCaptor.capture()); - final ClientTransaction transaction = mClientTransactionCaptor.getValue(); - // Check that none are enter pip request items. - transaction.getCallbacks().forEach(clientTransactionItem -> { - assertFalse(clientTransactionItem instanceof EnterPipRequestedItem); - }); + verify(mClientLifecycleManager, never()).scheduleTransaction(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 6790dc2e8733..afea8114d508 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -117,6 +117,20 @@ public class BackNavigationControllerTests extends WindowTestsBase { } @Test + public void noBackWhenMoveTaskToBack() { + Task taskA = createTask(mDefaultDisplay); + ActivityRecord recordA = createActivityRecord(taskA); + Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any()); + + final Task topTask = createTopTaskWithActivity(); + withSystemCallback(topTask); + // simulate moveTaskToBack + topTask.setVisibleRequested(false); + BackNavigationInfo backNavigationInfo = startBackNavigation(); + assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNull(); + } + + @Test public void backTypeCrossTaskWhenBackToPreviousTask() { Task taskA = createTask(mDefaultDisplay); ActivityRecord recordA = createActivityRecord(taskA); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 0d4c443ce1b0..c6796dc9e90d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -787,22 +787,44 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public void testOverrideOrientationIfNeeded_userFullscreenOverride_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); } + @Test + public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED)); + } @Test @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) public void testOverrideOrientationIfNeeded_userFullScreenOverrideOverSystem_returnsUser() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(true); assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); } @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_respectOrientationReqOverUserFullScreenAndSystem() { + spyOn(mController); + doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertNotEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); + } + + @Test public void testOverrideOrientationIfNeeded_userFullScreenOverrideDisabled_returnsUnchanged() { spyOn(mController); doReturn(false).when(mController).shouldApplyUserFullscreenOverride(); @@ -872,14 +894,6 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test - public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() { - prepareActivityThatShouldApplyUserFullscreenOverride(); - mDisplayContent.setIgnoreOrientationRequest(false); - - assertFalse(mController.shouldApplyUserFullscreenOverride()); - } - - @Test public void testShouldApplyUserFullscreenOverride_returnsTrue() { prepareActivityThatShouldApplyUserFullscreenOverride(); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 491d5b56c8e2..8de45b039f62 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -202,8 +202,7 @@ public class RecentsAnimationTest extends WindowTestsBase { any() /* starting */, anyInt() /* configChanges */, anyBoolean() /* preserveWindows */, anyBoolean() /* notifyClients */); doReturn(app).when(mAtm).getProcessController(eq(recentActivity.processName), anyInt()); - ClientLifecycleManager lifecycleManager = mAtm.getLifecycleManager(); - doNothing().when(lifecycleManager).scheduleTransaction(any()); + doNothing().when(mClientLifecycleManager).scheduleTransaction(any()); startRecentsActivity(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index a7c14c38b832..c3102e08489d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -31,6 +31,7 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -1301,6 +1302,27 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testShouldCreateCompatDisplayUserAspectRatioFullscreenOverride() { + setUpDisplaySizeWithApp(1000, 2500); + + // Make the task root resizable. + mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE; + + // Create an activity on the same task. + final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false, + RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + + // Simulate the user selecting the fullscreen user aspect ratio override + spyOn(activity.mWmService.mLetterboxConfiguration); + spyOn(activity.mLetterboxUiController); + doReturn(true).when(activity.mWmService.mLetterboxConfiguration) + .isUserAppAspectRatioFullscreenEnabled(); + doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(activity.mLetterboxUiController) + .getUserMinAspectRatioOverrideCode(); + assertFalse(activity.shouldCreateCompatDisplayInsets()); + } + + @Test @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS}) public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() { setUpDisplaySizeWithApp(1000, 1200); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java index 4a335944228a..6655932b060b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java @@ -31,7 +31,9 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -143,6 +145,15 @@ public class TaskSnapshotControllerTest extends WindowTestsBase { secureWindow.mAttrs.flags |= FLAG_SECURE; assertEquals(SNAPSHOT_MODE_APP_THEME, mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask())); + + // Verifies that if the snapshot can be cached, then getSnapshotMode should be respected. + // Otherwise a real snapshot can be taken even if the activity disables recents screenshot. + spyOn(mWm.mTaskSnapshotController); + final int disabledInRecentsTaskId = disabledWindow.getTask().mTaskId; + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, true /* updateCache */); + verify(mWm.mTaskSnapshotController, never()).prepareTaskSnapshot(any(), any()); + mAtm.takeTaskSnapshot(disabledInRecentsTaskId, false /* updateCache */); + verify(mWm.mTaskSnapshotController).prepareTaskSnapshot(any(), any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index a83caa4a4e95..dade3b91e0eb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -636,6 +636,7 @@ public class TransitionTests extends WindowTestsBase { transition.collect(app); controller.requestStartTransition(transition, null /* startTask */, remoteTransition, null /* displayChange */); + assertTrue(delegateProc.isRunningRemoteTransition()); testPlayer.startTransition(); app.onStartingWindowDrawn(); // The task appeared event should be deferred until transition ready. @@ -643,7 +644,6 @@ public class TransitionTests extends WindowTestsBase { testPlayer.onTransactionReady(app.getSyncTransaction()); assertTrue(task.taskAppearedReady()); assertTrue(playerProc.isRunningRemoteTransition()); - assertTrue(delegateProc.isRunningRemoteTransition()); assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); assertTrue(app.isVisible()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index eaeb8049b81a..d08ab51c9146 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -859,9 +859,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int callingUid = Process.FIRST_APPLICATION_UID; final int callingPid = 1234; final SurfaceControl surfaceControl = mock(SurfaceControl.class); - final IWindow window = mock(IWindow.class); - final IBinder windowToken = mock(IBinder.class); - when(window.asBinder()).thenReturn(windowToken); + final IBinder window = new Binder(); final IBinder focusGrantToken = mock(IBinder.class); final InputChannel inputChannel = new InputChannel(); @@ -879,9 +877,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int callingUid = Process.SYSTEM_UID; final int callingPid = 1234; final SurfaceControl surfaceControl = mock(SurfaceControl.class); - final IWindow window = mock(IWindow.class); - final IBinder windowToken = mock(IBinder.class); - when(window.asBinder()).thenReturn(windowToken); + final IBinder window = new Binder(); final IBinder focusGrantToken = mock(IBinder.class); final InputChannel inputChannel = new InputChannel(); @@ -901,9 +897,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int callingUid = Process.FIRST_APPLICATION_UID; final int callingPid = 1234; final SurfaceControl surfaceControl = mock(SurfaceControl.class); - final IWindow window = mock(IWindow.class); - final IBinder windowToken = mock(IBinder.class); - when(window.asBinder()).thenReturn(windowToken); + final IBinder window = new Binder(); final IBinder focusGrantToken = mock(IBinder.class); final InputChannel inputChannel = new InputChannel(); @@ -927,9 +921,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int callingUid = Process.SYSTEM_UID; final int callingPid = 1234; final SurfaceControl surfaceControl = mock(SurfaceControl.class); - final IWindow window = mock(IWindow.class); - final IBinder windowToken = mock(IBinder.class); - when(window.asBinder()).thenReturn(windowToken); + final IBinder window = new Binder(); final IBinder focusGrantToken = mock(IBinder.class); final InputChannel inputChannel = new InputChannel(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 1494f94f2052..28e0c6b65599 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -1337,8 +1337,8 @@ public class WindowOrganizerTests extends WindowTestsBase { // Since we have a window we have to wait for it to draw to finish sync. verify(mockCallback, never()).onTransactionReady(anyInt(), any()); - assertTrue(w1.useBLASTSync()); - assertTrue(w2.useBLASTSync()); + assertTrue(w1.syncNextBuffer()); + assertTrue(w2.syncNextBuffer()); // Make second (bottom) ready. If we started with the top, since activities fillsParent // by default, the sync would be considered finished. @@ -1348,16 +1348,16 @@ public class WindowOrganizerTests extends WindowTestsBase { assertEquals(SYNC_STATE_READY, w2.mSyncState); // Even though one Window finished drawing, both windows should still be using blast sync - assertTrue(w1.useBLASTSync()); - assertTrue(w2.useBLASTSync()); + assertTrue(w1.syncNextBuffer()); + assertTrue(w2.syncNextBuffer()); // A drawn window can complete the sync state automatically. w1.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; makeLastConfigReportedToClient(w1, true /* visible */); mWm.mSyncEngine.onSurfacePlacement(); verify(mockCallback).onTransactionReady(anyInt(), any()); - assertFalse(w1.useBLASTSync()); - assertFalse(w2.useBLASTSync()); + assertFalse(w1.syncNextBuffer()); + assertFalse(w2.syncNextBuffer()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index b89182d728ec..46cff8b82c6d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -306,29 +306,28 @@ public class WindowProcessControllerTests extends WindowTestsBase { @Test public void testCachedStateConfigurationChange() throws RemoteException { - final ClientLifecycleManager clientManager = mAtm.getLifecycleManager(); - doNothing().when(clientManager).scheduleTransaction(any(), any()); + doNothing().when(mClientLifecycleManager).scheduleTransaction(any(), any()); final IApplicationThread thread = mWpc.getThread(); final Configuration newConfig = new Configuration(mWpc.getConfiguration()); newConfig.densityDpi += 100; // Non-cached state will send the change directly. mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); - clearInvocations(clientManager); + clearInvocations(mClientLifecycleManager); mWpc.onConfigurationChanged(newConfig); - verify(clientManager).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager).scheduleTransaction(eq(thread), any()); // Cached state won't send the change. - clearInvocations(clientManager); + clearInvocations(mClientLifecycleManager); mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); newConfig.densityDpi += 100; mWpc.onConfigurationChanged(newConfig); - verify(clientManager, never()).scheduleTransaction(eq(thread), any()); + verify(mClientLifecycleManager, never()).scheduleTransaction(eq(thread), any()); // Cached -> non-cached will send the previous deferred config immediately. mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER); final ArgumentCaptor<ConfigurationChangeItem> captor = ArgumentCaptor.forClass(ConfigurationChangeItem.class); - verify(clientManager).scheduleTransaction(eq(thread), captor.capture()); + verify(mClientLifecycleManager).scheduleTransaction(eq(thread), captor.capture()); final ClientTransactionHandler client = mock(ClientTransactionHandler.class); captor.getValue().preExecute(client); final ArgumentCaptor<Configuration> configCaptor = diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 014d57dd9970..67384b2d9e18 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -551,7 +551,7 @@ public class WindowStateTests extends WindowTestsBase { final SurfaceControl.Transaction[] handledT = { null }; // The normal case that the draw transaction is applied with finishing drawing. win.applyWithNextDraw(t -> handledT[0] = t); - assertTrue(win.useBLASTSync()); + assertTrue(win.syncNextBuffer()); final SurfaceControl.Transaction drawT = new StubTransaction(); final SurfaceControl.Transaction currT = win.getSyncTransaction(); clearInvocations(currT); @@ -560,12 +560,12 @@ public class WindowStateTests extends WindowTestsBase { // The draw transaction should be merged to current transaction even if the state is hidden. verify(currT).merge(eq(drawT)); assertEquals(drawT, handledT[0]); - assertFalse(win.useBLASTSync()); + assertFalse(win.syncNextBuffer()); // If the window is gone before reporting drawn, the sync state should be cleared. win.applyWithNextDraw(t -> handledT[0] = t); win.destroySurfaceUnchecked(); - assertFalse(win.useBLASTSync()); + assertFalse(win.syncNextBuffer()); assertNotEquals(drawT, handledT[0]); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index e0ed642d3130..df4af112c087 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -152,6 +152,7 @@ class WindowTestsBase extends SystemServiceTestsBase { ActivityTaskManagerService mAtm; RootWindowContainer mRootWindowContainer; ActivityTaskSupervisor mSupervisor; + ClientLifecycleManager mClientLifecycleManager; WindowManagerService mWm; private final IWindow mIWindow = new TestIWindow(); private Session mTestSession; @@ -215,6 +216,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mAtm = mSystemServicesTestRule.getActivityTaskManagerService(); mSupervisor = mAtm.mTaskSupervisor; mRootWindowContainer = mAtm.mRootWindowContainer; + mClientLifecycleManager = mAtm.getLifecycleManager(); mWm = mSystemServicesTestRule.getWindowManagerService(); mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled; SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index f64ab22628d9..63de41f80c23 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -206,6 +206,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9; static final int MSG_UID_REMOVED = 10; static final int MSG_USER_STARTED = 11; + static final int MSG_NOTIFY_USAGE_EVENT_LISTENER = 12; private final Object mLock = new Object(); private Handler mHandler; @@ -315,6 +316,16 @@ public class UsageStatsService extends SystemService implements Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); return true; } + case MSG_NOTIFY_USAGE_EVENT_LISTENER: { + final int userId = msg.arg1; + final Event event = (Event) msg.obj; + synchronized (mUsageEventListeners) { + final int size = mUsageEventListeners.size(); + for (int i = 0; i < size; ++i) { + mUsageEventListeners.valueAt(i).onUsageEvent(userId, event); + } + } + } } return false; }; @@ -532,9 +543,6 @@ public class UsageStatsService extends SystemService implements } reportEvent(unlockEvent, userId); - mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, - userId, 0).sendToTarget(); - // Remove all the stats stored in system DE. deleteRecursively(new File(Environment.getDataSystemDeDirectory(userId), "usagestats")); @@ -546,6 +554,8 @@ public class UsageStatsService extends SystemService implements userService.persistActiveStats(); } } + + mIoHandler.obtainMessage(MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK, userId, 0).sendToTarget(); } /** @@ -1240,12 +1250,7 @@ public class UsageStatsService extends SystemService implements service.reportEvent(event); } - synchronized (mUsageEventListeners) { - final int size = mUsageEventListeners.size(); - for (int i = 0; i < size; ++i) { - mUsageEventListeners.valueAt(i).onUsageEvent(userId, event); - } - } + mIoHandler.obtainMessage(MSG_NOTIFY_USAGE_EVENT_LISTENER, userId, 0, event).sendToTarget(); } private String getUsageSourcePackage(Event event) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index bb6cf5246267..fb183754f921 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -52,9 +52,12 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA; -import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_EGRESS_LIMIT_REACHED; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_REMOTE_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__TRAINING_DATA_SECURITY_EXCEPTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID; import android.annotation.NonNull; @@ -73,7 +76,9 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Parcel; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; @@ -94,6 +99,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.infra.AndroidFuture; +import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.policy.AppOpsPolicy; import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener; @@ -180,6 +186,13 @@ abstract class DetectorSession { private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION = HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION; + private static final int HOTWORD_EVENT_TYPE_DETECTION = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; + private static final int HOTWORD_EVENT_TYPE_REJECTION = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; + private static final int HOTWORD_EVENT_TYPE_TRAINING_DATA = + HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; + private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool(); // TODO: This may need to be a Handler(looper) final ScheduledExecutorService mScheduledExecutorService; @@ -516,6 +529,7 @@ abstract class DetectorSession { if (result != null) { Slog.i(TAG, "Egressed 'hotword rejected result' " + "from hotword trusted process"); + logEgressSizeStats(result); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + result); } @@ -608,6 +622,7 @@ abstract class DetectorSession { Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult) + " bits from hotword trusted process"); + logEgressSizeStats(newResult); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); @@ -624,6 +639,32 @@ abstract class DetectorSession { mVoiceInteractionServiceUid); } + void logEgressSizeStats(HotwordTrainingData data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_TRAINING_DATA); + } + + void logEgressSizeStats(HotwordDetectedResult data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_DETECTION); + + } + + void logEgressSizeStats(HotwordRejectedResult data) { + logEgressSizeStats(data, HOTWORD_EVENT_TYPE_REJECTION); + } + + /** Logs event size stats for events egressed from trusted hotword detection service. */ + private void logEgressSizeStats(Parcelable data, int eventType) { + BackgroundThread.getExecutor().execute(() -> { + Parcel parcel = Parcel.obtain(); + parcel.writeValue(data); + int dataSizeBytes = parcel.dataSize(); + parcel.recycle(); + + HotwordMetricsLogger.writeHotwordDataEgressSize(eventType, dataSizeBytes, + getDetectorType(), mVoiceInteractionServiceUid); + }); + } + /** Used to send training data. * * @hide @@ -723,6 +764,7 @@ abstract class DetectorSession { mVoiceInteractionServiceUid); throw e; } + logEgressSizeStats(data); } void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java index 6418f3e83114..2938a58267d7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java @@ -186,6 +186,7 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); } + logEgressSizeStats(newResult); } } @@ -228,6 +229,7 @@ final class DspTrustedHotwordDetectorSession extends DetectorSession { if (mDebugHotwordLogging && result != null) { Slog.i(TAG, "Egressed rejected result: " + result); } + logEgressSizeStats(result); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java index f7b66a26ff68..ca72c85af6af 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordMetricsLogger.java @@ -34,6 +34,9 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENT import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; +import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; import android.content.Context; @@ -120,6 +123,16 @@ public final class HotwordMetricsLogger { } /** + * Logs hotword event egress size metrics. + */ + public static void writeHotwordDataEgressSize(int eventType, long eventSize, int detectorType, + int uid) { + int metricsDetectorType = getHotwordEventEgressSizeDetectorType(detectorType); + FrameworkStatsLog.write(FrameworkStatsLog.HOTWORD_EGRESS_SIZE_ATOM_REPORTED, + eventType, eventSize, metricsDetectorType, uid); + } + + /** * Starts a {@link LatencyTracker} log for the time it takes to show the * {@link android.service.voice.VoiceInteractionSession} system UI after a voice trigger. * @@ -224,4 +237,15 @@ public final class HotwordMetricsLogger { return AUDIO_EGRESS_NORMAL_DETECTOR; } } + + private static int getHotwordEventEgressSizeDetectorType(int detectorType) { + switch (detectorType) { + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_SOFTWARE; + case HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP; + default: + return HOTWORD_EVENT_EGRESS_SIZE__DETECTOR_TYPE__NORMAL_DETECTOR; + } + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java index 2e23eff7a179..9de7f9ad4922 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java @@ -179,6 +179,7 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { } Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult) + " bits from hotword trusted process"); + logEgressSizeStats(newResult); if (mDebugHotwordLogging) { Slog.i(TAG, "Egressed detected result: " + newResult); } @@ -194,6 +195,7 @@ final class SoftwareTrustedHotwordDetectorSession extends DetectorSession { HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE, HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED, mVoiceInteractionServiceUid); + logEgressSizeStats(result); // onRejected isn't allowed here, and we are not expecting it. } diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java index b1a7d819cd17..8c6e101f2c03 100644 --- a/telecomm/java/android/telecom/CallAttributes.java +++ b/telecomm/java/android/telecom/CallAttributes.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -83,6 +85,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING}) + @Retention(RetentionPolicy.SOURCE) public @interface Direction { } /** @@ -96,6 +99,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {AUDIO_CALL, VIDEO_CALL}) + @Retention(RetentionPolicy.SOURCE) public @interface CallType { } /** @@ -110,6 +114,7 @@ public final class CallAttributes implements Parcelable { /** @hide */ @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true) + @Retention(RetentionPolicy.SOURCE) public @interface CallCapability { } /** diff --git a/telephony/OWNERS b/telephony/OWNERS index 3158ad8fc58e..287aa653ef9a 100644 --- a/telephony/OWNERS +++ b/telephony/OWNERS @@ -7,10 +7,12 @@ rgreenwalt@google.com tgunn@google.com huiwang@google.com jayachandranc@google.com -chinmayd@google.com amruthr@google.com sasindran@google.com # Requiring TL ownership for new carrier config keys. per-file CarrierConfigManager.java=set noparent per-file CarrierConfigManager.java=amruthr@google.com,tgunn@google.com,rgreenwalt@google.com,satk@google.com + +#Domain Selection is jointly owned, add additional owners for domain selection specific files +per-file TransportSelectorCallback.java,WwanSelectorCallback.java,DomainSelectionService.java,DomainSelectionService.aidl,DomainSelector.java,EmergencyRegResult.java,EmergencyRegResult.aidl,IDomainSelectionServiceController.aidl,IDomainSelector.aidl,ITransportSelectorCallback.aidl,ITransportSelectorResultCallback.aidl,IWwanSelectorCallback.aidl,IWwanSelectorResultCallback.aidl=hwangoo@google.com,forestchoi@google.com,avinashmp@google.com,mkoon@google.com,seheele@google.com,radhikaagrawal@google.com diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index baeff06a5836..c124079ca2e3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8875,6 +8875,19 @@ public class CarrierConfigManager { KEY_PREFIX + "child_session_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of child session. + * Possible values are: + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "child_session_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for child session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8883,6 +8896,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_child_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for child session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_child_session_aead_algorithms_int_array"; + + /** * Time in seconds after which the IKE session is terminated if rekey procedure is not * successful. If not set or set to <= 0, default value is 3600 seconds. */ @@ -8919,6 +8942,19 @@ public class CarrierConfigManager { KEY_PREFIX + "ike_session_encryption_aes_ctr_key_size_int_array"; /** + * List of supported key sizes for AES Galois/Counter Mode (GCM) encryption mode + * of IKE session. + * Possible values - + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_UNUSED}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_128}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_192}, + * {@link android.net.ipsec.ike.SaProposal#KEY_LEN_AES_256} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY = + KEY_PREFIX + "ike_session_encryption_aes_gcm_key_size_int_array"; + + /** * List of supported encryption algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CBC}, * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_CTR} @@ -8927,6 +8963,16 @@ public class CarrierConfigManager { KEY_PREFIX + "supported_ike_session_encryption_algorithms_int_array"; /** + * List of supported AEAD algorithms for IKE session. Possible values are + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_8}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_12}, + * {@link android.net.ipsec.ike.SaProposal#ENCRYPTION_ALGORITHM_AES_GCM_16} + */ + @FlaggedApi(Flags.FLAG_ENABLE_AEAD_ALGORITHMS) + public static final String KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY = + KEY_PREFIX + "supported_ike_session_aead_algorithms_int_array"; + + /** * List of supported integrity algorithms for IKE session. Possible values are * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_NONE}, * {@link android.net.ipsec.ike.SaProposal#INTEGRITY_ALGORITHM_HMAC_SHA1_96}, @@ -9156,9 +9202,13 @@ public class CarrierConfigManager { KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_IKE_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_CHILD_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY, new int[] {SaProposal.ENCRYPTION_ALGORITHM_AES_CBC}); defaults.putIntArray( + KEY_SUPPORTED_CHILD_SESSION_AEAD_ALGORITHMS_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY, new int[] { SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96, @@ -9207,6 +9257,10 @@ public class CarrierConfigManager { SaProposal.KEY_LEN_AES_192, SaProposal.KEY_LEN_AES_256}); defaults.putIntArray( + KEY_IKE_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( + KEY_CHILD_SESSION_AES_GCM_KEY_SIZE_INT_ARRAY, new int[] {}); + defaults.putIntArray( KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC}); defaults.putIntArray( diff --git a/telephony/java/android/telephony/NumberVerificationCallback.java b/telephony/java/android/telephony/NumberVerificationCallback.java index b00c57351589..71df1f2fa28c 100644 --- a/telephony/java/android/telephony/NumberVerificationCallback.java +++ b/telephony/java/android/telephony/NumberVerificationCallback.java @@ -20,6 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * A callback for number verification. After a request for number verification is received, * the system will call {@link #onCallReceived(String)} if a phone call was received from a number @@ -34,6 +37,7 @@ public interface NumberVerificationCallback { REASON_TOO_MANY_CALLS, REASON_CONCURRENT_REQUESTS, REASON_IN_ECBM, REASON_IN_EMERGENCY_CALL}, prefix = {"REASON_"}) + @Retention(RetentionPolicy.SOURCE) @interface NumberVerificationFailureReason {} /** diff --git a/telephony/java/android/telephony/PinResult.java b/telephony/java/android/telephony/PinResult.java index b8c1ffe58371..14713c7d4b12 100644 --- a/telephony/java/android/telephony/PinResult.java +++ b/telephony/java/android/telephony/PinResult.java @@ -25,6 +25,8 @@ import android.os.Parcelable; import com.android.internal.telephony.PhoneConstants; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -46,6 +48,7 @@ public final class PinResult implements Parcelable { PIN_RESULT_TYPE_FAILURE, PIN_RESULT_TYPE_ABORTED, }) + @Retention(RetentionPolicy.SOURCE) public @interface PinResultType {} /** diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index 7f1c14b877d2..b568f07135b8 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -16,6 +16,8 @@ package android.telephony; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -37,8 +39,11 @@ import android.telephony.data.ApnSetting; import android.telephony.data.DataCallResponse; import android.telephony.data.Qos; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; @@ -66,6 +71,53 @@ public final class PreciseDataConnectionState implements Parcelable { private final LinkProperties mLinkProperties; private final ApnSetting mApnSetting; private final Qos mDefaultQos; + private final @NetworkValidationStatus int mNetworkValidationStatus; + + /** @hide */ + @IntDef(prefix = "NETWORK_VALIDATION_", value = { + NETWORK_VALIDATION_UNSUPPORTED, + NETWORK_VALIDATION_NOT_REQUESTED, + NETWORK_VALIDATION_IN_PROGRESS, + NETWORK_VALIDATION_SUCCESS, + NETWORK_VALIDATION_FAILURE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkValidationStatus {} + + /** + * Unsupported. The unsupported state is used when the data network cannot support the network + * validation function for the current data connection state. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public static final int NETWORK_VALIDATION_UNSUPPORTED = 0; + + /** + * Not Requested. The not requested status is used when the data network supports the network + * validation function, but no network validation is being performed yet. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public static final int NETWORK_VALIDATION_NOT_REQUESTED = 1; + + /** + * In progress. The in progress state is used when the network validation process for the data + * network is in progress. This state is followed by either success or failure. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public static final int NETWORK_VALIDATION_IN_PROGRESS = 2; + + /** + * Success. The Success status is used when network validation has been completed for the data + * network and the result is successful. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public static final int NETWORK_VALIDATION_SUCCESS = 3; + + /** + * Failure. The Failure status is used when network validation has been completed for the data + * network and the result is failure. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public static final int NETWORK_VALIDATION_FAILURE = 4; /** * Constructor @@ -87,7 +139,7 @@ public final class PreciseDataConnectionState implements Parcelable { .setApnTypeBitmask(apnTypes) .setApnName(apn) .setEntryName(apn) - .build(), null); + .build(), null, NETWORK_VALIDATION_UNSUPPORTED); } @@ -109,7 +161,8 @@ public final class PreciseDataConnectionState implements Parcelable { private PreciseDataConnectionState(@TransportType int transportType, int id, @DataState int state, @NetworkType int networkType, @Nullable LinkProperties linkProperties, @DataFailureCause int failCause, - @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos) { + @Nullable ApnSetting apnSetting, @Nullable Qos defaultQos, + @NetworkValidationStatus int networkValidationStatus) { mTransportType = transportType; mId = id; mState = state; @@ -118,6 +171,7 @@ public final class PreciseDataConnectionState implements Parcelable { mFailCause = failCause; mApnSetting = apnSetting; mDefaultQos = defaultQos; + mNetworkValidationStatus = networkValidationStatus; } /** @@ -140,6 +194,7 @@ public final class PreciseDataConnectionState implements Parcelable { mDefaultQos = in.readParcelable( Qos.class.getClassLoader(), android.telephony.data.Qos.class); + mNetworkValidationStatus = in.readInt(); } /** @@ -289,6 +344,16 @@ public final class PreciseDataConnectionState implements Parcelable { return mDefaultQos; } + /** + * Returns the network validation state. + * + * @return the network validation status of the data call + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public @NetworkValidationStatus int getNetworkValidationStatus() { + return mNetworkValidationStatus; + } + @Override public int describeContents() { return 0; @@ -304,6 +369,7 @@ public final class PreciseDataConnectionState implements Parcelable { out.writeInt(mFailCause); out.writeParcelable(mApnSetting, flags); out.writeParcelable(mDefaultQos, flags); + out.writeInt(mNetworkValidationStatus); } public static final @NonNull Parcelable.Creator<PreciseDataConnectionState> CREATOR @@ -321,7 +387,7 @@ public final class PreciseDataConnectionState implements Parcelable { @Override public int hashCode() { return Objects.hash(mTransportType, mId, mState, mNetworkType, mFailCause, - mLinkProperties, mApnSetting, mDefaultQos); + mLinkProperties, mApnSetting, mDefaultQos, mNetworkValidationStatus); } @@ -337,7 +403,8 @@ public final class PreciseDataConnectionState implements Parcelable { && mFailCause == that.mFailCause && Objects.equals(mLinkProperties, that.mLinkProperties) && Objects.equals(mApnSetting, that.mApnSetting) - && Objects.equals(mDefaultQos, that.mDefaultQos); + && Objects.equals(mDefaultQos, that.mDefaultQos) + && mNetworkValidationStatus == that.mNetworkValidationStatus; } @NonNull @@ -354,11 +421,34 @@ public final class PreciseDataConnectionState implements Parcelable { sb.append(", link properties: " + mLinkProperties); sb.append(", default QoS: " + mDefaultQos); sb.append(", fail cause: " + DataFailCause.toString(mFailCause)); + sb.append(", network validation status: " + + networkValidationStatusToString(mNetworkValidationStatus)); return sb.toString(); } /** + * Convert a network validation status to string. + * + * @param networkValidationStatus network validation status. + * @return string of validation status. + * + * @hide + */ + @NonNull + public static String networkValidationStatusToString( + @NetworkValidationStatus int networkValidationStatus) { + switch (networkValidationStatus) { + case NETWORK_VALIDATION_UNSUPPORTED: return "unsupported"; + case NETWORK_VALIDATION_NOT_REQUESTED: return "not requested"; + case NETWORK_VALIDATION_IN_PROGRESS: return "in progress"; + case NETWORK_VALIDATION_SUCCESS: return "success"; + case NETWORK_VALIDATION_FAILURE: return "failure"; + default: return Integer.toString(networkValidationStatus); + } + } + + /** * {@link PreciseDataConnectionState} builder * * @hide @@ -394,6 +484,10 @@ public final class PreciseDataConnectionState implements Parcelable { /** The Default QoS for this EPS/5GS bearer or null otherwise */ private @Nullable Qos mDefaultQos; + /** The network validation status for the data connection. */ + private @NetworkValidationStatus int mNetworkValidationStatus = + NETWORK_VALIDATION_UNSUPPORTED; + /** * Set the transport type of the data connection. * @@ -486,13 +580,27 @@ public final class PreciseDataConnectionState implements Parcelable { } /** + * Set the network validation state for the data connection. + * + * @param networkValidationStatus the network validation status of the data call + * @return The builder + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public @NonNull Builder setNetworkValidationStatus( + @NetworkValidationStatus int networkValidationStatus) { + mNetworkValidationStatus = networkValidationStatus; + return this; + } + + /** * Build the {@link PreciseDataConnectionState} instance. * * @return The {@link PreciseDataConnectionState} instance */ public PreciseDataConnectionState build() { return new PreciseDataConnectionState(mTransportType, mId, mState, mNetworkType, - mLinkProperties, mFailCause, mApnSetting, mDefaultQos); + mLinkProperties, mFailCause, mApnSetting, mDefaultQos, + mNetworkValidationStatus); } } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 1c5761dcebe0..b96914e59c0e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3400,6 +3400,7 @@ public class TelephonyManager { SIM_STATE_LOADED, SIM_STATE_PRESENT, }) + @Retention(RetentionPolicy.SOURCE) public @interface SimState {} /** @@ -10170,6 +10171,7 @@ public class TelephonyManager { CALL_COMPOSER_STATUS_ON, CALL_COMPOSER_STATUS_OFF, }) + @Retention(RetentionPolicy.SOURCE) public @interface CallComposerStatus {} /** @@ -13157,7 +13159,7 @@ public class TelephonyManager { CARRIER_RESTRICTION_STATUS_RESTRICTED, CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER }) - + @Retention(RetentionPolicy.SOURCE) public @interface CarrierRestrictionStatus { } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 4b1a72695e50..3e8787281f85 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -951,8 +951,8 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @return True if the PDU session for this APN should always be on and false otherwise - * @hide */ + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) public boolean isAlwaysOn() { return mAlwaysOn; } @@ -2282,9 +2282,9 @@ public class ApnSetting implements Parcelable { * See 3GPP TS 23.501 section 5.6.13 * * @param alwaysOn the always on status to set for this APN - * @hide */ - public Builder setAlwaysOn(boolean alwaysOn) { + @FlaggedApi(Flags.FLAG_APN_SETTING_FIELD_SUPPORT_FLAG) + public @NonNull Builder setAlwaysOn(boolean alwaysOn) { this.mAlwaysOn = alwaysOn; return this; } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index c7f0c5f753db..9dd83d1438e2 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -17,6 +17,7 @@ package android.telephony.data; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -27,9 +28,11 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Annotation.DataFailureCause; import android.telephony.DataFailCause; +import android.telephony.PreciseDataConnectionState; import android.telephony.data.ApnSetting.ProtocolType; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.flags.Flags; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -123,7 +126,6 @@ public final class DataCallResponse implements Parcelable { * Indicates that the pdu session id is not set. */ public static final int PDU_SESSION_ID_NOT_SET = 0; - private final @DataFailureCause int mCause; private final long mSuggestedRetryTime; private final int mId; @@ -143,6 +145,7 @@ public final class DataCallResponse implements Parcelable { private final List<QosBearerSession> mQosBearerSessions; private final NetworkSliceInfo mSliceInfo; private final List<TrafficDescriptor> mTrafficDescriptors; + private final @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus; /** * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. @@ -185,7 +188,8 @@ public final class DataCallResponse implements Parcelable { HANDOVER_FAILURE_MODE_LEGACY, PDU_SESSION_ID_NOT_SET, null /* defaultQos */, Collections.emptyList() /* qosBearerSessions */, null /* sliceInfo */, - Collections.emptyList() /* trafficDescriptors */); + Collections.emptyList(), /* trafficDescriptors */ + PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED); } private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id, @@ -196,7 +200,8 @@ public final class DataCallResponse implements Parcelable { @HandoverFailureMode int handoverFailureMode, int pduSessionId, @Nullable Qos defaultQos, @NonNull List<QosBearerSession> qosBearerSessions, @Nullable NetworkSliceInfo sliceInfo, - @NonNull List<TrafficDescriptor> trafficDescriptors) { + @NonNull List<TrafficDescriptor> trafficDescriptors, + @PreciseDataConnectionState.NetworkValidationStatus int networkValidationStatus) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; @@ -216,6 +221,7 @@ public final class DataCallResponse implements Parcelable { mQosBearerSessions = new ArrayList<>(qosBearerSessions); mSliceInfo = sliceInfo; mTrafficDescriptors = new ArrayList<>(trafficDescriptors); + mNetworkValidationStatus = networkValidationStatus; if (mLinkStatus == LINK_STATUS_ACTIVE || mLinkStatus == LINK_STATUS_DORMANT) { @@ -270,6 +276,7 @@ public final class DataCallResponse implements Parcelable { source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class); + mNetworkValidationStatus = source.readInt(); } /** @@ -442,6 +449,17 @@ public final class DataCallResponse implements Parcelable { return Collections.unmodifiableList(mTrafficDescriptors); } + /** + * Return the network validation status that was initiated by {@link + * DataService.DataServiceProvider#requestValidation} + * + * @return The network validation status of data connection. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public @PreciseDataConnectionState.NetworkValidationStatus int getNetworkValidationStatus() { + return mNetworkValidationStatus; + } + @NonNull @Override public String toString() { @@ -466,6 +484,8 @@ public final class DataCallResponse implements Parcelable { .append(" qosBearerSessions=").append(mQosBearerSessions) .append(" sliceInfo=").append(mSliceInfo) .append(" trafficDescriptors=").append(mTrafficDescriptors) + .append(" networkValidationStatus=").append(PreciseDataConnectionState + .networkValidationStatusToString(mNetworkValidationStatus)) .append("}"); return sb.toString(); } @@ -504,7 +524,8 @@ public final class DataCallResponse implements Parcelable { && mQosBearerSessions.containsAll(other.mQosBearerSessions) // non-null && Objects.equals(mSliceInfo, other.mSliceInfo) && mTrafficDescriptors.size() == other.mTrafficDescriptors.size() // non-null - && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); // non-null + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors) // non-null + && mNetworkValidationStatus == other.mNetworkValidationStatus; } @Override @@ -513,7 +534,7 @@ public final class DataCallResponse implements Parcelable { mInterfaceName, Set.copyOf(mAddresses), Set.copyOf(mDnsAddresses), Set.copyOf(mGatewayAddresses), Set.copyOf(mPcscfAddresses), mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, mDefaultQos, Set.copyOf(mQosBearerSessions), - mSliceInfo, Set.copyOf(mTrafficDescriptors)); + mSliceInfo, Set.copyOf(mTrafficDescriptors), mNetworkValidationStatus); } @Override @@ -542,6 +563,7 @@ public final class DataCallResponse implements Parcelable { dest.writeList(mQosBearerSessions); dest.writeParcelable(mSliceInfo, flags); dest.writeList(mTrafficDescriptors); + dest.writeInt(mNetworkValidationStatus); } public static final @android.annotation.NonNull Parcelable.Creator<DataCallResponse> CREATOR = @@ -629,6 +651,9 @@ public final class DataCallResponse implements Parcelable { private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>(); + private @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus = + PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED; + /** * Default constructor for Builder. */ @@ -905,6 +930,20 @@ public final class DataCallResponse implements Parcelable { } /** + * Set the network validation status that corresponds to the state of the network validation + * request started by {@link DataService.DataServiceProvider#requestValidation} + * + * @param status The network validation status. + * @return The same instance of the builder. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public @NonNull Builder setNetworkValidationStatus( + @PreciseDataConnectionState.NetworkValidationStatus int status) { + mNetworkValidationStatus = status; + return this; + } + + /** * Build the DataCallResponse. * * @return the DataCallResponse object. @@ -913,7 +952,8 @@ public final class DataCallResponse implements Parcelable { return new DataCallResponse(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, mInterfaceName, mAddresses, mDnsAddresses, mGatewayAddresses, mPcscfAddresses, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, - mDefaultQos, mQosBearerSessions, mSliceInfo, mTrafficDescriptors); + mDefaultQos, mQosBearerSessions, mSliceInfo, mTrafficDescriptors, + mNetworkValidationStatus); } } } diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index d8b2cbebdf28..80e91a330185 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -16,6 +16,8 @@ package android.telephony.data; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -26,6 +28,7 @@ import android.app.Service; import android.content.Intent; import android.net.LinkProperties; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -36,6 +39,9 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.flags.Flags; +import com.android.internal.util.FunctionalUtils; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -44,6 +50,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Base class of data service. Services that extend DataService must register the service in @@ -113,11 +121,14 @@ public abstract class DataService extends Service { private static final int DATA_SERVICE_REQUEST_REGISTER_APN_UNTHROTTLED = 14; private static final int DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED = 15; private static final int DATA_SERVICE_INDICATION_APN_UNTHROTTLED = 16; + private static final int DATA_SERVICE_REQUEST_VALIDATION = 17; private final HandlerThread mHandlerThread; private final DataServiceHandler mHandler; + private final Executor mHandlerExecutor; + private final SparseArray<DataServiceProvider> mServiceMap = new SparseArray<>(); /** @hide */ @@ -379,6 +390,43 @@ public abstract class DataService extends Service { } } + /** + * Request validation check to see if the network is working properly for a given data call. + * + * <p>This request is completed immediately after submitting the request to the data service + * provider and receiving {@link DataServiceCallback.ResultCode}, and progress status or + * validation results are notified through {@link + * DataCallResponse#getNetworkValidationStatus}. + * + * <p> If the network validation request is submitted successfully, {@link + * DataServiceCallback#RESULT_SUCCESS} is passed to {@code resultCodeCallback}. If the + * network validation feature is not supported by the data service provider itself, {@link + * DataServiceCallback#RESULT_ERROR_UNSUPPORTED} is passed to {@code resultCodeCallback}. + * See {@link DataServiceCallback.ResultCode} for the type of response that indicates + * whether the request was successfully submitted or had an error. + * + * <p>In response to this network validation request, providers can validate the data call + * in their own way. For example, in IWLAN, the DPD (Dead Peer Detection) can be used as a + * tool to check whether a data call is alive. + * + * @param cid The identifier of the data call which is provided in {@link DataCallResponse} + * @param executor The callback executor for the response. + * @param resultCodeCallback Listener for the {@link DataServiceCallback.ResultCode} that + * request validation to the DataService and checks if the request has been submitted. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public void requestValidation(int cid, + @NonNull @CallbackExecutor Executor executor, + @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) { + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null"); + + Log.d(TAG, "requestValidation: " + cid); + + // The default implementation is to return unsupported. + executor.execute(() -> resultCodeCallback + .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED)); + } /** * Notify the system that current data call list changed. Data service must invoke this @@ -537,6 +585,17 @@ public abstract class DataService extends Service { } } + private static final class ValidationRequest { + public final int cid; + public final Executor executor; + public final IIntegerConsumer callback; + ValidationRequest(int cid, Executor executor, IIntegerConsumer callback) { + this.cid = cid; + this.executor = executor; + this.callback = callback; + } + } + private class DataServiceHandler extends Handler { DataServiceHandler(Looper looper) { @@ -679,6 +738,15 @@ public abstract class DataService extends Service { loge("Failed to call onApnUnthrottled. " + e); } break; + case DATA_SERVICE_REQUEST_VALIDATION: + if (serviceProvider == null) break; + ValidationRequest validationRequest = (ValidationRequest) message.obj; + serviceProvider.requestValidation( + validationRequest.cid, + validationRequest.executor, + FunctionalUtils + .ignoreRemoteException(validationRequest.callback::accept)); + break; } } } @@ -691,6 +759,7 @@ public abstract class DataService extends Service { mHandlerThread.start(); mHandler = new DataServiceHandler(mHandlerThread.getLooper()); + mHandlerExecutor = new HandlerExecutor(mHandler); log("Data service created"); } @@ -853,6 +922,18 @@ public abstract class DataService extends Service { mHandler.obtainMessage(DATA_SERVICE_REQUEST_UNREGISTER_APN_UNTHROTTLED, slotIndex, 0, callback).sendToTarget(); } + + @Override + public void requestValidation(int slotIndex, int cid, IIntegerConsumer resultCodeCallback) { + if (resultCodeCallback == null) { + loge("requestValidation: resultCodeCallback is null"); + return; + } + ValidationRequest validationRequest = + new ValidationRequest(cid, mHandlerExecutor, resultCodeCallback); + mHandler.obtainMessage(DATA_SERVICE_REQUEST_VALIDATION, + slotIndex, 0, validationRequest).sendToTarget(); + } } private void log(String s) { diff --git a/telephony/java/android/telephony/data/IDataService.aidl b/telephony/java/android/telephony/data/IDataService.aidl index 134694694a0e..15f88815ec6b 100644 --- a/telephony/java/android/telephony/data/IDataService.aidl +++ b/telephony/java/android/telephony/data/IDataService.aidl @@ -22,6 +22,8 @@ import android.telephony.data.IDataServiceCallback; import android.telephony.data.NetworkSliceInfo; import android.telephony.data.TrafficDescriptor; +import com.android.internal.telephony.IIntegerConsumer; + /** * {@hide} */ @@ -46,4 +48,5 @@ oneway interface IDataService void cancelHandover(int slotId, int cid, IDataServiceCallback callback); void registerForUnthrottleApn(int slotIndex, IDataServiceCallback callback); void unregisterForUnthrottleApn(int slotIndex, IDataServiceCallback callback); + void requestValidation(int slotId, int cid, IIntegerConsumer callback); } diff --git a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl index 32ffdbc2121c..bdd212afd4b0 100644 --- a/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl +++ b/telephony/java/android/telephony/data/IQualifiedNetworksServiceCallback.aidl @@ -16,6 +16,8 @@ package android.telephony.data; +import com.android.internal.telephony.IIntegerConsumer; + /** * The qualified networks service call back interface * @hide @@ -23,4 +25,5 @@ package android.telephony.data; oneway interface IQualifiedNetworksServiceCallback { void onQualifiedNetworkTypesChanged(int apnTypes, in int[] qualifiedNetworkTypes); + void onNetworkValidationRequested(int networkCapability, IIntegerConsumer callback); } diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java index 56f0f9f13772..c3ba09248298 100644 --- a/telephony/java/android/telephony/data/QualifiedNetworksService.java +++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java @@ -16,6 +16,8 @@ package android.telephony.data; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.Service; @@ -29,13 +31,23 @@ import android.os.RemoteException; import android.telephony.AccessNetworkConstants; import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.telephony.Annotation.ApnType; +import android.telephony.Annotation.NetCapability; +import android.telephony.PreciseDataConnectionState; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.IIntegerConsumer; +import com.android.internal.telephony.flags.FeatureFlags; +import com.android.internal.telephony.flags.FeatureFlagsImpl; +import com.android.internal.telephony.flags.Flags; +import com.android.internal.util.FunctionalUtils; import com.android.telephony.Rlog; import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Base class of the qualified networks service, which is a vendor service providing up-to-date @@ -69,6 +81,10 @@ public abstract class QualifiedNetworksService extends Service { private static final int QNS_UPDATE_QUALIFIED_NETWORKS = 4; private static final int QNS_APN_THROTTLE_STATUS_CHANGED = 5; private static final int QNS_EMERGENCY_DATA_NETWORK_PREFERRED_TRANSPORT_CHANGED = 6; + private static final int QNS_REQUEST_NETWORK_VALIDATION = 7; + + /** Feature flags */ + private static final FeatureFlags sFeatureFlag = new FeatureFlagsImpl(); private final HandlerThread mHandlerThread; @@ -208,6 +224,72 @@ public abstract class QualifiedNetworksService extends Service { } /** + * Request network validation to the connected data network for given a network capability. + * + * <p>This network validation can only be performed when a data network is in connected + * state, and will not be triggered if the data network does not support network validation + * feature or network validation is not in connected state. + * + * <p>See {@link DataServiceCallback.ResultCode} for the type of response that indicates + * whether the request was successfully submitted or had an error. + * + * <p>If network validation is requested, monitor network validation status in {@link + * PreciseDataConnectionState#getNetworkValidationStatus()}. + * + * @param networkCapability A network capability. (Note that only APN-type capabilities are + * supported. + * @param executor executor The callback executor that responds whether the request has been + * successfully submitted or not. + * @param resultCodeCallback A callback to determine whether the request was successfully + * submitted or not. + */ + @FlaggedApi(Flags.FLAG_NETWORK_VALIDATION) + public void requestNetworkValidation( + @NetCapability int networkCapability, + @NonNull @CallbackExecutor Executor executor, + @NonNull @DataServiceCallback.ResultCode Consumer<Integer> resultCodeCallback) { + Objects.requireNonNull(executor, "executor cannot be null"); + Objects.requireNonNull(resultCodeCallback, "resultCodeCallback cannot be null"); + + if (!sFeatureFlag.networkValidation()) { + loge("networkValidation feature is disabled"); + executor.execute( + () -> + resultCodeCallback.accept( + DataServiceCallback.RESULT_ERROR_UNSUPPORTED)); + return; + } + + IIntegerConsumer callback = new IIntegerConsumer.Stub() { + @Override + public void accept(int result) { + executor.execute(() -> resultCodeCallback.accept(result)); + } + }; + + // Move to the internal handler and process it. + mHandler.obtainMessage( + QNS_REQUEST_NETWORK_VALIDATION, + mSlotIndex, + 0, + new NetworkValidationRequestData(networkCapability, callback)) + .sendToTarget(); + } + + /** Process a network validation request on the internal handler. */ + private void onRequestNetworkValidation(NetworkValidationRequestData data) { + try { + log("onRequestNetworkValidation"); + // Callback to request a network validation. + mCallback.onNetworkValidationRequested(data.mNetworkCapability, data.mCallback); + } catch (RemoteException | NullPointerException e) { + loge("Failed to call onRequestNetworkValidation. " + e); + FunctionalUtils.ignoreRemoteException(data.mCallback::accept) + .accept(DataServiceCallback.RESULT_ERROR_UNSUPPORTED); + } + } + + /** * Called when the qualified networks provider is removed. The extended class should * implement this method to perform cleanup works. */ @@ -280,6 +362,10 @@ public abstract class QualifiedNetworksService extends Service { if (provider == null) break; provider.onUpdateQualifiedNetworkTypes(message.arg2, (int[]) message.obj); break; + + case QNS_REQUEST_NETWORK_VALIDATION: + if (provider == null) break; + provider.onRequestNetworkValidation((NetworkValidationRequestData) message.obj); } } } @@ -364,6 +450,17 @@ public abstract class QualifiedNetworksService extends Service { } } + private static final class NetworkValidationRequestData { + final @NetCapability int mNetworkCapability; + final IIntegerConsumer mCallback; + + private NetworkValidationRequestData(@NetCapability int networkCapability, + @NonNull IIntegerConsumer callback) { + mNetworkCapability = networkCapability; + mCallback = callback; + } + } + private void log(String s) { Rlog.d(TAG, s); } diff --git a/telephony/java/android/telephony/data/ThrottleStatus.java b/telephony/java/android/telephony/data/ThrottleStatus.java index 0335c6868340..0dff6ff705ca 100644 --- a/telephony/java/android/telephony/data/ThrottleStatus.java +++ b/telephony/java/android/telephony/data/ThrottleStatus.java @@ -27,6 +27,8 @@ import android.os.SystemClock; import android.telephony.AccessNetworkConstants; import android.telephony.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -52,6 +54,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.THROTTLE_TYPE_NONE, ThrottleStatus.THROTTLE_TYPE_ELAPSED_TIME, }) + @Retention(RetentionPolicy.SOURCE) public @interface ThrottleType { } @@ -76,6 +79,7 @@ public final class ThrottleStatus implements Parcelable { ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, ThrottleStatus.RETRY_TYPE_HANDOVER, }) + @Retention(RetentionPolicy.SOURCE) public @interface RetryType { } diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java index 76394feaed66..e2df0d44fda8 100644 --- a/telephony/java/android/telephony/ims/MediaQualityStatus.java +++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -49,6 +51,7 @@ public final class MediaQualityStatus implements Parcelable { MEDIA_SESSION_TYPE_AUDIO, MEDIA_SESSION_TYPE_VIDEO, }) + @Retention(RetentionPolicy.SOURCE) public @interface MediaSessionType {} /** diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java index f367e404a35b..39c9d8b94d3a 100644 --- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java +++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -35,6 +37,7 @@ public final class RcsClientConfiguration implements Parcelable { /**@hide*/ @StringDef(prefix = "RCS_PROFILE_", value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3, RCS_PROFILE_2_4}) + @Retention(RetentionPolicy.SOURCE) public @interface StringRcsProfile {} /** diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 15a20cb91d04..4c53f8ab9bca 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3147,4 +3147,16 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void unregisterForSatelliteCapabilitiesChanged(int subId, in ISatelliteCapabilitiesCallback callback); + + /** + * This API can be used by only CTS to override the cached value for the device overlay config + * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether + * outgoing satellite datagrams should be sent to modem in demo mode. + * + * @param shouldSendToDemoMode Whether send datagram in demo mode should be sent to satellite + * modem or not. + * + * @return {@code true} if the operation is successful, {@code false} otherwise. + */ + boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode); } diff --git a/tests/CtsSurfaceControlTestsStaging/OWNERS b/tests/CtsSurfaceControlTestsStaging/OWNERS index 438b7f2ce335..8ad3446c7b52 100644 --- a/tests/CtsSurfaceControlTestsStaging/OWNERS +++ b/tests/CtsSurfaceControlTestsStaging/OWNERS @@ -1 +1 @@ -include platform/cts:/tests/tests/graphics/OWNERS
\ No newline at end of file +include platform/cts:/tests/tests/graphics/src/android/graphics/OWNERS
\ No newline at end of file diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp index 6cda8f050987..b4a435909953 100644 --- a/tests/MotionPrediction/Android.bp +++ b/tests/MotionPrediction/Android.bp @@ -26,5 +26,8 @@ package { android_app { name: "MotionPrediction", srcs: ["**/*.kt"], + kotlincflags: [ + "-Werror", + ], sdk_version: "current", } diff --git a/tests/MultiDeviceInput/Android.bp b/tests/MultiDeviceInput/Android.bp new file mode 100644 index 000000000000..3c80873168b4 --- /dev/null +++ b/tests/MultiDeviceInput/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_app { + name: "MultiDeviceInput", + srcs: ["**/*.kt"], + kotlincflags: [ + "-Werror", + ], + sdk_version: "current", +} diff --git a/tests/MultiDeviceInput/AndroidManifest.xml b/tests/MultiDeviceInput/AndroidManifest.xml new file mode 100644 index 000000000000..ed8cadb9519b --- /dev/null +++ b/tests/MultiDeviceInput/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.multideviceinput"> + + <application android:allowBackup="false" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity android:name=".MainActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tests/MultiDeviceInput/OWNERS b/tests/MultiDeviceInput/OWNERS new file mode 100644 index 000000000000..c88bfe97cab9 --- /dev/null +++ b/tests/MultiDeviceInput/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/INPUT_OWNERS diff --git a/tests/MultiDeviceInput/README.md b/tests/MultiDeviceInput/README.md new file mode 100644 index 000000000000..5fcdeda6e5d7 --- /dev/null +++ b/tests/MultiDeviceInput/README.md @@ -0,0 +1,19 @@ +# MultiDeviceInput test app # + +This demo app is for manual testing of the multi-device input feature. +It creates two windows - one on the left and one on the right. You can use different input devices +in these windows. + +## Installation ## +Install this using: +``` +APP=MultiDeviceInput; m $APP && adb install $ANDROID_PRODUCT_OUT/system/app/$APP/$APP.apk +``` + +## Features ## + +* Touch in one window, use stylus in another window, at the same time +* Visualize hovering stylus +* Pinch zoom in one window to affect the line thickness in another window +* Check whether stylus rejects touch in the same window +* (in the future) Check stylus and touch operation in the same window diff --git a/tests/MultiDeviceInput/res/layout/activity_main.xml b/tests/MultiDeviceInput/res/layout/activity_main.xml new file mode 100644 index 000000000000..a6a6f891a034 --- /dev/null +++ b/tests/MultiDeviceInput/res/layout/activity_main.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context="test.multideviceinput.MainActivity"> + +</LinearLayout> diff --git a/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..cde69bcccec6 --- /dev/null +++ b/tests/MultiDeviceInput/res/mipmap-hdpi/ic_launcher.png diff --git a/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..c133a0cbd379 --- /dev/null +++ b/tests/MultiDeviceInput/res/mipmap-mdpi/ic_launcher.png diff --git a/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..bfa42f0e7b91 --- /dev/null +++ b/tests/MultiDeviceInput/res/mipmap-xhdpi/ic_launcher.png diff --git a/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..324e72cdd748 --- /dev/null +++ b/tests/MultiDeviceInput/res/mipmap-xxhdpi/ic_launcher.png diff --git a/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png b/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 000000000000..aee44e138434 --- /dev/null +++ b/tests/MultiDeviceInput/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/tests/MultiDeviceInput/res/values-w820dp/dimens.xml b/tests/MultiDeviceInput/res/values-w820dp/dimens.xml new file mode 100644 index 000000000000..b14a560efd72 --- /dev/null +++ b/tests/MultiDeviceInput/res/values-w820dp/dimens.xml @@ -0,0 +1,20 @@ +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/tests/MultiDeviceInput/res/values/colors.xml b/tests/MultiDeviceInput/res/values/colors.xml new file mode 100644 index 000000000000..c37df9f7b428 --- /dev/null +++ b/tests/MultiDeviceInput/res/values/colors.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <color name="colorPrimary">#3F51B5</color> + <color name="colorPrimaryDark">#303F9F</color> + <color name="colorAccent">#FF4081</color> +</resources> diff --git a/tests/MultiDeviceInput/res/values/dimens.xml b/tests/MultiDeviceInput/res/values/dimens.xml new file mode 100644 index 000000000000..bdb8ede1c913 --- /dev/null +++ b/tests/MultiDeviceInput/res/values/dimens.xml @@ -0,0 +1,19 @@ +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/tests/MultiDeviceInput/res/values/strings.xml b/tests/MultiDeviceInput/res/values/strings.xml new file mode 100644 index 000000000000..3827c344f87f --- /dev/null +++ b/tests/MultiDeviceInput/res/values/strings.xml @@ -0,0 +1,17 @@ +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="app_name">Simultaneous touch and stylus</string> +</resources> diff --git a/tests/MultiDeviceInput/res/values/styles.xml b/tests/MultiDeviceInput/res/values/styles.xml new file mode 100644 index 000000000000..a563e7e09706 --- /dev/null +++ b/tests/MultiDeviceInput/res/values/styles.xml @@ -0,0 +1,23 @@ +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <!-- Base application theme. --> + <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="android:colorPrimary">@color/colorPrimary</item> + <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="android:colorAccent">@color/colorAccent</item> + </style> +</resources> diff --git a/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt b/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt new file mode 100644 index 000000000000..b5bd9ca746aa --- /dev/null +++ b/tests/MultiDeviceInput/src/test/multideviceinput/DrawingView.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.multideviceinput + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.InputDevice.SOURCE_STYLUS +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_HOVER_EXIT +import android.view.MotionEvent.ACTION_UP +import android.view.ScaleGestureDetector +import android.view.View + +import java.util.Vector + +private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) { + // Correct implementation here would require us to build a set of pointers and then iterate + // through them. Instead, we are taking a few shortcuts and ignore some of the events, which + // causes occasional gaps in the drawings. + if (from.pointerCount != to.pointerCount) { + return + } + // Now, 'from' is guaranteed to have as many pointers as the 'to' event. It doesn't + // necessarily mean they are the same pointers, though. + for (p in 0..<from.pointerCount) { + val x0 = from.getX(p) + val y0 = from.getY(p) + if (to.getPointerId(p) == from.getPointerId(p)) { + // This only works when the i-th pointer in "to" is the same pointer + // as the i-th pointer in "from"`. It's not guaranteed by the input APIs, + // but it works in practice. + val x1 = to.getX(p) + val y1 = to.getY(p) + // Ignoring historical data here for simplicity + canvas.drawLine(x0, y0, x1, y1, paint) + } + } +} + +private fun drawCircle(canvas: Canvas, event: MotionEvent, paint: Paint, radius: Float) { + val x = event.getX() + val y = event.getY() + canvas.drawCircle(x, y, radius, paint) +} + +/** + * Draw the current stroke + */ +class DrawingView : View { + private val TAG = "DrawingView" + + private var myState: SharedScaledPointerSize? = null + private var otherState: SharedScaledPointerSize? = null + + constructor( + context: Context, + myState: SharedScaledPointerSize, + otherState: SharedScaledPointerSize + ) : super(context) { + this.myState = myState + this.otherState = otherState + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + val touchEvents = mutableMapOf<Int, Vector<Pair<MotionEvent, Paint>>>() + val hoverEvents = mutableMapOf<Int, MotionEvent>() + + val scaleGestureListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + + override fun onScaleBegin(scaleGestureDetector: ScaleGestureDetector): Boolean { + return true + } + + override fun onScale(scaleGestureDetector: ScaleGestureDetector): Boolean { + val scaleFactor = scaleGestureDetector.scaleFactor + when (otherState?.state) { + PointerState.DOWN -> { + otherState?.lineSize = (otherState?.lineSize ?: 5f) * scaleFactor + } + PointerState.HOVER -> { + otherState?.circleSize = (otherState?.circleSize ?: 20f) * scaleFactor + } + else -> {} + } + return true + } + } + private val scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener, null) + + private var touchPaint = Paint() + private var stylusPaint = Paint() + + private fun init() { + touchPaint.color = Color.RED + touchPaint.setStrokeWidth(5f) + stylusPaint.color = Color.YELLOW + stylusPaint.setStrokeWidth(5f) + + setOnHoverListener { _, event -> processHoverEvent(event); true } + } + + private fun processTouchEvent(event: MotionEvent) { + scaleGestureDetector.onTouchEvent(event) + if (event.actionMasked == ACTION_DOWN) { + touchEvents.remove(event.deviceId) + myState?.state = PointerState.DOWN + } else if (event.actionMasked == ACTION_UP) { + myState?.state = PointerState.NONE + } + var vec = touchEvents.getOrPut(event.deviceId) { Vector<Pair<MotionEvent, Paint>>() } + + val paint = if (event.isFromSource(SOURCE_STYLUS)) { + val size = myState?.lineSize ?: 5f + stylusPaint.setStrokeWidth(size) + Paint(stylusPaint) + } else { + val size = myState?.lineSize ?: 5f + touchPaint.setStrokeWidth(size) + Paint(touchPaint) + } + vec.add(Pair(MotionEvent.obtain(event), paint)) + invalidate() + } + + private fun processHoverEvent(event: MotionEvent) { + hoverEvents.remove(event.deviceId) + if (event.getActionMasked() != ACTION_HOVER_EXIT) { + hoverEvents.put(event.deviceId, MotionEvent.obtain(event)) + myState?.state = PointerState.HOVER + } else { + myState?.state = PointerState.NONE + } + invalidate() + } + + public override fun onTouchEvent(event: MotionEvent): Boolean { + processTouchEvent(event) + return true + } + + public override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + // Draw touch and stylus MotionEvents + for ((_, vec) in touchEvents ) { + for (i in 1 until vec.size) { + drawLine(canvas, vec[i - 1].first, vec[i].first, vec[i].second) + } + } + // Draw hovers + for ((_, event) in hoverEvents ) { + if (event.isFromSource(SOURCE_STYLUS)) { + val size = myState?.circleSize ?: 20f + drawCircle(canvas, event, stylusPaint, size) + } else { + val size = myState?.circleSize ?: 20f + drawCircle(canvas, event, touchPaint, size) + } + } + } +} diff --git a/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt b/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt new file mode 100644 index 000000000000..911208579d4f --- /dev/null +++ b/tests/MultiDeviceInput/src/test/multideviceinput/MainActivity.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.multideviceinput + +import android.app.Activity +import android.graphics.Color +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets.Type +import android.view.WindowManager + + +enum class PointerState { + DOWN, // One or more pointer(s) down, lines are being drawn + HOVER, // Pointer is hovering + NONE, // Nothing is touching or hovering +} + +data class SharedScaledPointerSize( + var lineSize: Float, + var circleSize: Float, + var state: PointerState +) + +class MainActivity : Activity() { + val TAG = "MultiDeviceInput" + private val leftState = SharedScaledPointerSize(5f, 20f, PointerState.NONE) + private val rightState = SharedScaledPointerSize(5f, 20f, PointerState.NONE) + private lateinit var left: View + private lateinit var right: View + + override fun onResume() { + super.onResume() + + val wm = getSystemService(WindowManager::class.java) + val wmlp = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION) + wmlp.flags = (wmlp.flags or + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + + val windowMetrics = windowManager.currentWindowMetrics + val insets = windowMetrics.windowInsets.getInsetsIgnoringVisibility(Type.systemBars()) + val width = windowMetrics.bounds.width() - insets.left - insets.right + val height = windowMetrics.bounds.height() - insets.top - insets.bottom + + wmlp.width = width * 24 / 50 + wmlp.height = height * 35 / 50 + + val vglp = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + + wmlp.setTitle("Left -- " + getPackageName()) + wmlp.gravity = Gravity.CENTER_VERTICAL or Gravity.START + left = DrawingView(this, leftState, rightState) + left.setBackgroundColor(Color.LTGRAY) + left.setLayoutParams(vglp) + wm.addView(left, wmlp) + + wmlp.setTitle("Right -- " + getPackageName()) + wmlp.gravity = Gravity.CENTER_VERTICAL or Gravity.END + right = DrawingView(this, rightState, leftState) + right.setBackgroundColor(Color.LTGRAY) + right.setLayoutParams(vglp) + wm.addView(right, wmlp) + } +} diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index 421ceb797c15..07b733830bd3 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -50,7 +50,7 @@ import org.junit.runners.JUnit4; public class VibratorManagerServicePermissionTest { private static final String PACKAGE_NAME = "com.android.framework.permission.tests"; - private static final int DISPLAY_ID = 1; + private static final int DEVICE_ID = 1; private static final CombinedVibration EFFECT = CombinedVibration.createParallel( VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -107,7 +107,7 @@ public class VibratorManagerServicePermissionTest { @Test public void testVibrateWithoutPermissionFails() throws RemoteException { expectSecurityException("VIBRATE"); - mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -117,7 +117,7 @@ public class VibratorManagerServicePermissionTest { throws RemoteException { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS, + mVibratorService.vibrate(Process.myUid(), DEVICE_ID, PACKAGE_NAME, EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -127,7 +127,7 @@ public class VibratorManagerServicePermissionTest { expectSecurityException("UPDATE_APP_OPS_STATS"); getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE); - mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS, "testVibrate", new Binder()); } @@ -137,7 +137,7 @@ public class VibratorManagerServicePermissionTest { getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( Manifest.permission.VIBRATE, Manifest.permission.UPDATE_APP_OPS_STATS); - mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS, + mVibratorService.vibrate(Process.SYSTEM_UID, DEVICE_ID, "android", EFFECT, ATTRS, "testVibrate", new Binder()); } diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index fff8f78a5d01..412aa9bf88ab 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -120,6 +120,7 @@ cc_library_host_static { "io/Util.cpp", "io/ZipArchive.cpp", "link/AutoVersioner.cpp", + "link/FeatureFlagsFilter.cpp", "link/ManifestFixer.cpp", "link/NoDefaultResourceRemover.cpp", "link/PrivateAttributeMover.cpp", diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 159c6fd9ef58..c638873873dc 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2514,6 +2514,28 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } } + // Parse the feature flag values. An argument that starts with '@' points to a file to read flag + // values from. + std::vector<std::string> all_feature_flags_args; + for (const std::string& arg : feature_flags_args_) { + if (util::StartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) { + context.GetDiagnostics()->Error(android::DiagMessage(path) << error); + return 1; + } + } else { + all_feature_flags_args.push_back(arg); + } + } + + for (const std::string& arg : all_feature_flags_args) { + if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { + return 1; + } + } + if (context.GetPackageType() != PackageType::kStaticLib && stable_id_file_path_) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path_.value(), &options_.stable_id_map)) { diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index a08f385b2270..26713fd92264 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -17,11 +17,17 @@ #ifndef AAPT2_LINK_H #define AAPT2_LINK_H +#include <optional> #include <regex> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <vector> #include "Command.h" #include "Resource.h" #include "androidfw/IDiagnostics.h" +#include "cmd/Util.h" #include "format/binary/TableFlattener.h" #include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" @@ -72,6 +78,7 @@ struct LinkOptions { bool use_sparse_encoding = false; std::unordered_set<std::string> extensions_to_not_compress; std::optional<std::regex> regex_to_not_compress; + FeatureFlagValues feature_flag_values; // Static lib options. bool no_static_lib_packages = false; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index a92f24b82547..678d84628015 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -113,6 +113,56 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std return std::move(filter); } +bool ParseFeatureFlagsParameter(StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values) { + if (arg.empty()) { + return true; + } + + for (StringPiece flag_and_value : util::Tokenize(arg, ',')) { + std::vector<std::string> parts = util::Split(flag_and_value, '='); + if (parts.empty()) { + continue; + } + + if (parts.size() > 2) { + diag->Error(android::DiagMessage() + << "Invalid feature flag and optional value '" << flag_and_value + << "'. Must be in the format 'flag_name[=true|false]"); + return false; + } + + StringPiece flag_name = util::TrimWhitespace(parts[0]); + if (flag_name.empty()) { + diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg); + return false; + } + + std::optional<bool> flag_value = {}; + if (parts.size() == 2) { + StringPiece str_flag_value = util::TrimWhitespace(parts[1]); + if (!str_flag_value.empty()) { + flag_value = ResourceUtils::ParseBool(parts[1]); + if (!flag_value.has_value()) { + diag->Error(android::DiagMessage() << "Invalid value for feature flag '" << flag_and_value + << "'. Value must be 'true' or 'false'"); + return false; + } + } + } + + if (auto [it, inserted] = + out_feature_flag_values->try_emplace(std::string(flag_name), flag_value); + !inserted) { + // We are allowing the same flag to appear multiple times, last value wins. + diag->Note(android::DiagMessage() + << "Value for feature flag '" << flag_name << "' was given more than once"); + it->second = flag_value; + } + } + return true; +} + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the minSdk. Otherwise the resources that have had // their SDK version stripped due to minSdk won't ever match. diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 712c07b71695..9ece5dd4d720 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -17,8 +17,13 @@ #ifndef AAPT_SPLIT_UTIL_H #define AAPT_SPLIT_UTIL_H +#include <functional> +#include <map> +#include <memory> +#include <optional> #include <regex> #include <set> +#include <string> #include <unordered_set> #include "AppInfo.h" @@ -32,6 +37,8 @@ namespace aapt { +using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>; + // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, @@ -48,6 +55,13 @@ bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args, android::IDiagnostics* diag); +// Parses a feature flags parameter, which can contain one or more pairs of flag names and optional +// values, and fills in `out_feature_flag_values` with the parsed values. The pairs in the argument +// are separated by ',' and the name is separated from the value by '=' if there is a value given. +// Example arg: "flag1=true,flag2=false,flag3=,flag4" where flag3 and flag4 have no given value. +bool ParseFeatureFlagsParameter(android::StringPiece arg, android::IDiagnostics* diag, + FeatureFlagValues* out_feature_flag_values); + // Adjust the SplitConstraints so that their SDK version is stripped if it // is less than or equal to the min_sdk. Otherwise the resources that have had // their SDK version stripped due to min_sdk won't ever match. diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 139bfbcd0f41..723d87ed0af3 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -25,6 +25,7 @@ #include "util/Files.h" using ::android::ConfigDescription; +using testing::Pair; using testing::UnorderedElementsAre; namespace aapt { @@ -354,6 +355,51 @@ TEST (UtilTest, ParseSplitParameters) { EXPECT_CONFIG_EQ(constraints, expected_configuration); } +TEST(UtilTest, ParseFeatureFlagsParameter_Empty) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("", diagnostics, &feature_flag_values)); + EXPECT_TRUE(feature_flag_values.empty()); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_TooManyParts) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=bar=baz", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_NoNameGiven) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,=false", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_InvalidValue) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_FALSE(ParseFeatureFlagsParameter("foo=true,bar=42", diagnostics, &feature_flag_values)); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE( + ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values)); + EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)), + Pair("bar", std::optional<bool>(true)))); +} + +TEST(UtilTest, ParseFeatureFlagsParameter_Valid) { + auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics(); + FeatureFlagValues feature_flag_values; + ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics, + &feature_flag_values)); + EXPECT_THAT(feature_flag_values, + UnorderedElementsAre(Pair("foo", std::optional<bool>(true)), + Pair("bar", std::optional<bool>(false)), + Pair("baz", std::nullopt), Pair("quux", std::nullopt))); +} + TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp new file mode 100644 index 000000000000..fdf3f74d4e18 --- /dev/null +++ b/tools/aapt2/link/FeatureFlagsFilter.cpp @@ -0,0 +1,104 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/FeatureFlagsFilter.h" + +#include <string_view> + +#include "androidfw/IDiagnostics.h" +#include "androidfw/Source.h" +#include "util/Util.h" +#include "xml/XmlDom.h" +#include "xml/XmlUtil.h" + +using ::aapt::xml::Element; +using ::aapt::xml::Node; +using ::aapt::xml::NodeCast; + +namespace aapt { + +class FlagsVisitor : public xml::Visitor { + public: + explicit FlagsVisitor(android::IDiagnostics* diagnostics, + const FeatureFlagValues& feature_flag_values, + const FeatureFlagsFilterOptions& options) + : diagnostics_(diagnostics), feature_flag_values_(feature_flag_values), options_(options) { + } + + void Visit(xml::Element* node) override { + std::erase_if(node->children, + [this](std::unique_ptr<xml::Node>& node) { return ShouldRemove(node); }); + VisitChildren(node); + } + + bool HasError() const { + return has_error_; + } + + private: + bool ShouldRemove(std::unique_ptr<xml::Node>& node) { + if (const auto* el = NodeCast<Element>(node.get())) { + auto* attr = el->FindAttribute(xml::kSchemaAndroid, "featureFlag"); + if (attr == nullptr) { + return false; + } + + bool negated = false; + std::string_view flag_name = util::TrimWhitespace(attr->value); + if (flag_name.starts_with('!')) { + negated = true; + flag_name = flag_name.substr(1); + } + + if (auto it = feature_flag_values_.find(std::string(flag_name)); + it != feature_flag_values_.end()) { + if (it->second.has_value()) { + if (options_.remove_disabled_elements) { + // Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag" + return *it->second == negated; + } + } else if (options_.flags_must_have_value) { + diagnostics_->Error(android::DiagMessage(node->line_number) + << "attribute 'android:featureFlag' has flag '" << flag_name + << "' without a true/false value from --feature_flags parameter"); + has_error_ = true; + return false; + } + } else if (options_.fail_on_unrecognized_flags) { + diagnostics_->Error(android::DiagMessage(node->line_number) + << "attribute 'android:featureFlag' has flag '" << flag_name + << "' not found in flags from --feature_flags parameter"); + has_error_ = true; + return false; + } + } + + return false; + } + + android::IDiagnostics* diagnostics_; + const FeatureFlagValues& feature_flag_values_; + const FeatureFlagsFilterOptions& options_; + bool has_error_ = false; +}; + +bool FeatureFlagsFilter::Consume(IAaptContext* context, xml::XmlResource* doc) { + FlagsVisitor visitor(context->GetDiagnostics(), feature_flag_values_, options_); + doc->root->Accept(&visitor); + return !visitor.HasError(); +} + +} // namespace aapt diff --git a/tools/aapt2/link/FeatureFlagsFilter.h b/tools/aapt2/link/FeatureFlagsFilter.h new file mode 100644 index 000000000000..1d342a71b996 --- /dev/null +++ b/tools/aapt2/link/FeatureFlagsFilter.h @@ -0,0 +1,79 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <optional> +#include <string> +#include <unordered_map> +#include <utility> + +#include "android-base/macros.h" +#include "cmd/Util.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +struct FeatureFlagsFilterOptions { + // If true, elements whose featureFlag values are false (i.e., disabled feature) will be removed. + bool remove_disabled_elements = true; + + // If true, `Consume()` will return false (error) if a flag was found that is not in + // `feature_flag_values`. + bool fail_on_unrecognized_flags = true; + + // If true, `Consume()` will return false (error) if a flag was found whose value in + // `feature_flag_values` is not defined (std::nullopt). + bool flags_must_have_value = true; +}; + +// Looks for the `android:featureFlag` attribute in each XML element, validates the flag names and +// values, and removes elements according to the values in `feature_flag_values`. An element will be +// removed if the flag's given value is FALSE. A "!" before the flag name in the attribute indicates +// a boolean NOT operation, i.e., an element will be removed if the flag's given value is TRUE. For +// example, if the XML is the following: +// +// <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> +// <permission android:name="FOO" android:featureFlag="!flag" +// android:protectionLevel="normal" /> +// <permission android:name="FOO" android:featureFlag="flag" +// android:protectionLevel="dangerous" /> +// </manifest> +// +// If `feature_flag_values` contains {"flag", true}, then the <permission> element with +// protectionLevel="normal" will be removed, and the <permission> element with +// protectionLevel="normal" will be kept. +// +// The `Consume()` function will return false if there is an invalid flag found (see +// FeatureFlagsFilterOptions for customizing the filter's validation behavior). Do not use the XML +// further if there are errors as there may be elements removed already. +class FeatureFlagsFilter : public IXmlResourceConsumer { + public: + explicit FeatureFlagsFilter(FeatureFlagValues feature_flag_values, + FeatureFlagsFilterOptions options) + : feature_flag_values_(std::move(feature_flag_values)), options_(options) { + } + + bool Consume(IAaptContext* context, xml::XmlResource* doc) override; + + private: + DISALLOW_COPY_AND_ASSIGN(FeatureFlagsFilter); + + const FeatureFlagValues feature_flag_values_; + const FeatureFlagsFilterOptions options_; +}; + +} // namespace aapt diff --git a/tools/aapt2/link/FeatureFlagsFilter_test.cpp b/tools/aapt2/link/FeatureFlagsFilter_test.cpp new file mode 100644 index 000000000000..53086cc30f18 --- /dev/null +++ b/tools/aapt2/link/FeatureFlagsFilter_test.cpp @@ -0,0 +1,236 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "link/FeatureFlagsFilter.h" + +#include <string_view> + +#include "test/Test.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace aapt { + +// Returns null if there was an error from FeatureFlagsFilter. +std::unique_ptr<xml::XmlResource> VerifyWithOptions(std::string_view str, + const FeatureFlagValues& feature_flag_values, + const FeatureFlagsFilterOptions& options) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str); + FeatureFlagsFilter filter(feature_flag_values, options); + if (filter.Consume(test::ContextBuilder().Build().get(), doc.get())) { + return doc; + } + return {}; +} + +// Returns null if there was an error from FeatureFlagsFilter. +std::unique_ptr<xml::XmlResource> Verify(std::string_view str, + const FeatureFlagValues& feature_flag_values) { + return VerifyWithOptions(str, feature_flag_values, {}); +} + +TEST(FeatureFlagsFilterTest, NoFeatureFlagAttributes) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" /> + </manifest>)EOF", + {{"flag", false}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} +TEST(FeatureFlagsFilterTest, RemoveElementWithDisabledFlag) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="flag" /> + </manifest>)EOF", + {{"flag", false}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, IsNull()); +} + +TEST(FeatureFlagsFilterTest, RemoveElementWithNegatedEnabledFlag) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="!flag" /> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, IsNull()); +} + +TEST(FeatureFlagsFilterTest, KeepElementWithEnabledFlag) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="flag" /> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST(FeatureFlagsFilterTest, SideBySideEnabledAndDisabled) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="!flag" + android:protectionLevel="normal" /> + <permission android:name="FOO" android:featureFlag="flag" + android:protectionLevel="dangerous" /> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto children = root->GetChildElements(); + ASSERT_EQ(children.size(), 1); + auto attr = children[0]->FindAttribute(xml::kSchemaAndroid, "protectionLevel"); + ASSERT_THAT(attr, NotNull()); + ASSERT_EQ(attr->value, "dangerous"); +} + +TEST(FeatureFlagsFilterTest, RemoveDeeplyNestedElement) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <application> + <provider /> + <activity> + <layout android:featureFlag="!flag" /> + </activity> + </application> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto application = root->FindChild({}, "application"); + ASSERT_THAT(application, NotNull()); + auto activity = application->FindChild({}, "activity"); + ASSERT_THAT(activity, NotNull()); + auto maybe_removed = activity->FindChild({}, "layout"); + ASSERT_THAT(maybe_removed, IsNull()); +} + +TEST(FeatureFlagsFilterTest, KeepDeeplyNestedElement) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <application> + <provider /> + <activity> + <layout android:featureFlag="flag" /> + </activity> + </application> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto application = root->FindChild({}, "application"); + ASSERT_THAT(application, NotNull()); + auto activity = application->FindChild({}, "activity"); + ASSERT_THAT(activity, NotNull()); + auto maybe_removed = activity->FindChild({}, "layout"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST(FeatureFlagsFilterTest, FailOnEmptyFeatureFlagAttribute) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag=" " /> + </manifest>)EOF", + {{"flag", false}}); + ASSERT_THAT(doc, IsNull()); +} + +TEST(FeatureFlagsFilterTest, FailOnFlagWithNoGivenValue) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="flag" /> + </manifest>)EOF", + {{"flag", std::nullopt}}); + ASSERT_THAT(doc, IsNull()); +} + +TEST(FeatureFlagsFilterTest, FailOnUnrecognizedFlag) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="unrecognized" /> + </manifest>)EOF", + {{"flag", true}}); + ASSERT_THAT(doc, IsNull()); +} + +TEST(FeatureFlagsFilterTest, FailOnMultipleValidationErrors) { + auto doc = Verify(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="bar" /> + <permission android:name="FOO" android:featureFlag="unrecognized" /> + </manifest>)EOF", + {{"flag", std::nullopt}}); + ASSERT_THAT(doc, IsNull()); +} + +TEST(FeatureFlagsFilterTest, OptionRemoveDisabledElementsIsFalse) { + auto doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="flag" /> + </manifest>)EOF", + {{"flag", false}}, {.remove_disabled_elements = false}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST(FeatureFlagsFilterTest, OptionFlagsMustHaveValueIsFalse) { + auto doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="flag" /> + </manifest>)EOF", + {{"flag", std::nullopt}}, {.flags_must_have_value = false}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST(FeatureFlagsFilterTest, OptionFailOnUnrecognizedFlagsIsFalse) { + auto doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> + <permission android:name="FOO" android:featureFlag="unrecognized" /> + </manifest>)EOF", + {{"flag", true}}, {.fail_on_unrecognized_flags = false}); + ASSERT_THAT(doc, NotNull()); + auto root = doc->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +} // namespace aapt diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index 25abbace85de..c770b9ccc800 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java @@ -65,7 +65,7 @@ public class HostTestUtils { */ public static void onThrowMethodCalled() { // TODO: Maybe add call tracking? - throw new AssumptionViolatedException("This method is not supported on the host side"); + throw new RuntimeException("This method is not supported on the host side"); } /** diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index b133c2a1f26c..248121c63d78 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt @@ -377,7 +377,7 @@ class AnnotationBasedFilter( throw HostStubGenInternalException("Policy $policy shouldn't show up here") } - val suffix = getAnnotationField(an, "suffix") ?: return@let + val suffix = getAnnotationField(an, "suffix", false) ?: "\$ravenwood" val renameFrom = mn.name + suffix val renameTo = mn.name @@ -387,13 +387,17 @@ class AnnotationBasedFilter( } // This mn has "SubstituteWith". This means, - // 1. Re move the "rename-to" method, so add it to substitutedMethods. + // 1. Re move the "rename-to" method, so add it to substitutedMethods. policiesFromSubstitution[MethodKey(renameTo, mn.desc)] = FilterPolicy.Remove.withReason("substitute-to") + // If the policy is "stub", use "stub". + // Otherwise, it must be "keep" or "throw", but there's no point in using + // "throw", so let's use "keep". + val newPolicy = if (policy.needsInStub) policy else FilterPolicy.Keep // 2. We also keep the from-to in the map. policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] = - policy.withReason("substitute-from") + newPolicy.withReason("substitute-from") substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo) @@ -405,10 +409,11 @@ class AnnotationBasedFilter( /** * Return the (String) value of 'value' parameter from an annotation. */ - private fun getAnnotationField(an: AnnotationNode, name: String): String? { + private fun getAnnotationField(an: AnnotationNode, name: String, + required: Boolean = true): String? { try { val suffix = findAnnotationValueAsString(an, name) - if (suffix == null) { + if (suffix == null && required) { errors.onErrorFound("Annotation \"${an.desc}\" must have field $name") } return suffix @@ -438,4 +443,4 @@ class AnnotationBasedFilter( return ret } } -}
\ No newline at end of file +} diff --git a/tools/lint/README.md b/tools/lint/README.md index b235ad60c799..ff8e44229189 100644 --- a/tools/lint/README.md +++ b/tools/lint/README.md @@ -103,10 +103,15 @@ out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/and As noted above, this baseline file contains warnings too, which might be undesirable. For example, CI tools might surface these warnings in code reviews. In order to create this file without -warnings, we need to pass another flag to lint: `--nowarn`. The easiest way to do this is to -locally change the soong code in -[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75) -adding `cmd.Flag("--nowarn")` and running lint again. +warnings, we need to pass another flag to lint: `--nowarn`. One option is to add the flag to your +Android.bp file and then run lint again: + +``` + lint: { + extra_check_modules: ["AndroidFrameworkLintChecker"], + flags: ["--nowarn"], + } +``` # Documentation diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt index d41fee3fc0dc..24d203fd1116 100644 --- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -24,33 +24,31 @@ import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** - * Given a UMethod, determine if this method is - * the entrypoint to an interface generated by AIDL, - * returning the interface name if so, otherwise returning null + * Given a UMethod, determine if this method is the entrypoint to an interface + * generated by AIDL, returning the interface name if so, otherwise returning + * null */ fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { - if (!isContainedInSubclassOfStub(context, node)) return null - for (superMethod in node.findSuperMethods()) { - for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements - ?: continue) { - if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) { - return superMethod.containingClass?.name - } - } - } - return null + val containingStub = containingStub(context, node) ?: return null + val superMethod = node.findSuperMethods(containingStub) + if (superMethod.isEmpty()) return null + return containingStub.containingClass?.name } -fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { +/* Returns the containing Stub class if any. This is not sufficient to infer + * that the method itself extends an AIDL generated method. See + * getContainingAidlInterface for that purpose. + */ +fun containingStub(context: JavaContext, node: UMethod?): PsiClass? { var superClass = node?.containingClass?.superClass while (superClass != null) { - if (isStub(context, superClass)) return true + if (isStub(context, superClass)) return superClass superClass = superClass.superClass } - return false + return null } -fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { +private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 935badecf8d5..624a1987638e 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -20,6 +20,7 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.parcel.SaferParcelChecker +import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -37,6 +38,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, // TODO: Currently crashes due to OOM issue // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, ) diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt new file mode 100644 index 000000000000..6b50cfd9e5ab --- /dev/null +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt @@ -0,0 +1,87 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UMethod + +/** + * Ensures all AIDL-generated methods are annotated. + * + * This detector is run on system_server to validate that any method that may + * be exposed via an AIDL interface is permission-annotated. That is, it must + * have one of the following annotation: + * - @EnforcePermission + * - @RequiresNoPermission + * - @PermissionManuallyEnforced + */ +class PermissionAnnotationDetector : AidlImplementationDetector() { + + override fun visitAidlMethod( + context: JavaContext, + node: UMethod, + interfaceName: String, + body: UBlockExpression + ) { + if (context.evaluator.isAbstract(node)) return + + if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return + + context.report( + ISSUE_MISSING_PERMISSION_ANNOTATION, + node, + context.getLocation(node), + "The method ${node.name} is not permission-annotated." + ) + } + + companion object { + + private val EXPLANATION_MISSING_PERMISSION_ANNOTATION = """ + Interfaces that are exposed by system_server are required to have an annotation which + denotes the type of permission enforced. There are 3 possible options: + - @EnforcePermission + - @RequiresNoPermission + - @PermissionManuallyEnforced + See the documentation of each annotation for further details. + + The annotation on the Java implementation must be the same that the AIDL interface + definition. This is verified by a lint in the build system. + """.trimIndent() + + @JvmField + val ISSUE_MISSING_PERMISSION_ANNOTATION = Issue.create( + id = "MissingPermissionAnnotation", + briefDescription = "No permission annotation on exposed AIDL interface.", + explanation = EXPLANATION_MISSING_PERMISSION_ANNOTATION, + category = Category.CORRECTNESS, + priority = 5, + severity = Severity.ERROR, + implementation = Implementation( + PermissionAnnotationDetector::class.java, + Scope.JAVA_FILE_SCOPE + ), + enabledByDefault = false + ) + } +} diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt new file mode 100644 index 000000000000..bce848a2e3a7 --- /dev/null +++ b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt @@ -0,0 +1,134 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PermissionAnnotationDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = PermissionAnnotationDetector() + + override fun getIssues(): List<Issue> = listOf( + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + /** No issue scenario */ + + fun testDoesNotDetectIssuesInCorrectScenario() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingAnnotation() { + lint().files( + java( + """ + public class Bar extends IBar.Stub { + public void testMethod() { } + } + """ + ).indented(), + *stubs + ) + .run() + .expect( + """ + src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] + public void testMethod() { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + ) + } + + fun testNoIssueWhenExtendingWithAnotherSubclass() { + lint().files( + java( + """ + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() { } + // not an AIDL method, just another method + public void someRandomMethod() { } + } + """).indented(), + java( + """ + public class Baz extends Bar { + @Override + public void someRandomMethod() { } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + // A service with permission annotation on the method. + private val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ + ).indented() + + // A service with no permission annotation. + private val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(); + } + """ + ).indented() + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar) +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 83b8f163abee..4455a9cda3a8 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -168,7 +168,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { annotationInfo.origin == AnnotationOrigin.METHOD) { /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */ val uMethod = element as? UMethod ?: return - if (!isContainedInSubclassOfStub(context, uMethod)) { + if (getContainingAidlInterface(context, uMethod) == null) { return } val overridingMethod = element.sourcePsi as PsiMethod @@ -184,7 +184,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { if (context.evaluator.isAbstract(node)) return if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return - if (!isContainedInSubclassOfStub(context, node)) { + val stubClass = containingStub(context, node) + if (stubClass == null) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, node, @@ -196,7 +197,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { /* Check that we are connected to the super class */ val overridingMethod = node as PsiMethod - val parents = overridingMethod.findSuperMethods() + val parents = overridingMethod.findSuperMethods(stubClass) if (parents.isEmpty()) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt index d8afcb977594..2afca05f8130 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt @@ -176,6 +176,29 @@ class EnforcePermissionDetectorTest : LintDetectorTest() { """.addLineContinuation()) } + fun testDetectNoIssuesAnnotationOnNonStubMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass43 extends IFooMethod.Stub { + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), java( + """ + package test.pkg; + public class TestClass44 extends TestClass43 { + @Override + public void aRegularMethodNotPartOfStub() { + } + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + fun testDetectIssuesEmptyAnnotationOnMethod() { lint().files(java( """ 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 ebda6f1c5826..4f5e0e48c793 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityService.java @@ -70,14 +70,8 @@ public abstract class SharedConnectivityService extends Service { private List<HotspotNetwork> mHotspotNetworks = Collections.emptyList(); private List<KnownNetwork> mKnownNetworks = Collections.emptyList(); private SharedConnectivitySettingsState mSettingsState = null; - private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = - new HotspotNetworkConnectionStatus.Builder() - .setStatus(HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); - private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = - new KnownNetworkConnectionStatus.Builder() - .setStatus(KnownNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN) - .setExtras(Bundle.EMPTY).build(); + private HotspotNetworkConnectionStatus mHotspotNetworkConnectionStatus = null; + private KnownNetworkConnectionStatus mKnownNetworkConnectionStatus = null; // Used for testing private CountDownLatch mCountDownLatch; diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java index c6f67987746a..48ac82dc54a8 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/service/SharedConnectivityServiceTest.java @@ -394,6 +394,26 @@ public class SharedConnectivityServiceTest { verify(mCallback, never()).onKnownNetworkConnectionStatusChanged(any()); } + @Test + public void getHotspotNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getHotspotNetworkConnectionStatus()).isNull(); + } + + @Test + public void getKnownNetworkConnectionStatus_withoutUpdate_returnsNull() + throws RemoteException { + SharedConnectivityService service = createService(); + ISharedConnectivityService.Stub binder = + (ISharedConnectivityService.Stub) service.onBind(new Intent()); + + assertThat(binder.getKnownNetworkConnectionStatus()).isNull(); + } + private FakeSharedConnectivityService createService() { FakeSharedConnectivityService service = new FakeSharedConnectivityService(); service.attachBaseContext(mContext); |