diff options
575 files changed, 14134 insertions, 4714 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 52200bfcfdf6..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", @@ -220,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"], } @@ -249,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", @@ -722,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/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 4333cd40351c..4256d51b09e4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7864,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"; } @@ -22807,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(); @@ -22825,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[]); @@ -22843,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); @@ -22932,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(); @@ -23036,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(); @@ -33155,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); @@ -33496,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 { @@ -33761,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); @@ -43694,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"; @@ -43709,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"; @@ -44738,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 { 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 5a8209f69dcf..a7ea753cb422 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3331,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 { @@ -4763,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 { @@ -14790,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(); @@ -14826,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); @@ -14905,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); @@ -14966,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/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/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 60965a84f6d2..41c90b96dc84 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -212,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/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/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java index cc12949b48f3..a4d532712cfe 100644 --- a/core/java/android/content/pm/LauncherActivityInfo.java +++ b/core/java/android/content/pm/LauncherActivityInfo.java @@ -22,18 +22,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; -import android.graphics.Paint; import android.graphics.drawable.Drawable; -import android.icu.text.UnicodeSet; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; import android.util.DisplayMetrics; -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Objects; - /** * A representation of an activity that can belong to this user or a managed * profile associated with this user. It can be used to query the label, icon @@ -43,10 +36,6 @@ public class LauncherActivityInfo { private final PackageManager mPm; private final LauncherActivityInfoInternal mInternal; - private static final UnicodeSet TRIMMABLE_CHARACTERS = - new UnicodeSet("[[:White_Space:][:Default_Ignorable_Code_Point:][:gc=Cc:]]", - /* ignoreWhitespace= */ false).freeze(); - /** * Create a launchable activity object for a given ResolveInfo and user. * @@ -83,28 +72,13 @@ public class LauncherActivityInfo { } /** - * Retrieves the label for the activity. The returned label can be different - * from {@link ActivityInfo#loadLabel(PackageManager)} or - * {@link PackageItemInfo#loadLabel(PackageManager)}. The returned result is trimmed. - * If the activity's label is empty, use the application's label instead. - * If the application's label is still empty, use the package name instead. + * Retrieves the label for the activity. * - * @return The label for the activity. If the activity's label is empty, - * return the application's label instead. If the application's label - * is still empty, return the package name instead. + * @return The label for the activity. */ public CharSequence getLabel() { - CharSequence label = trim(getActivityInfo().loadLabel(mPm)); - // If the trimmed label is empty, use application's label instead - if (TextUtils.isEmpty(label)) { - label = trim(getApplicationInfo().loadLabel(mPm)); - // If the trimmed label is still empty, use package name instead - if (TextUtils.isEmpty(label)) { - label = getComponentName().getPackageName(); - } - } // TODO: Go through LauncherAppsService - return label; + return getActivityInfo().loadLabel(mPm); } /** @@ -206,149 +180,4 @@ public class LauncherActivityInfo { return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser()); } - - /** - * If the {@code ch} is trimmable, return {@code true}. Otherwise, return - * {@code false}. If the count of the code points of {@code ch} doesn't - * equal 1, return {@code false}. - * <p> - * There are two types of the trimmable characters. - * 1. The character is one of the Default_Ignorable_Code_Point in - * <a href=" - * https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt"> - * DerivedCoreProperties.txt</a>, the White_Space in <a href= - * "https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt">PropList.txt - * </a> or category Cc. - * <p> - * 2. The character is not supported in the current system font. - * {@link android.graphics.Paint#hasGlyph(String)} - * <p> - * - */ - private static boolean isTrimmable(@NonNull Paint paint, @NonNull CharSequence ch) { - Objects.requireNonNull(paint); - Objects.requireNonNull(ch); - - // if ch is empty or it is not a character (i,e, the count of code - // point doesn't equal one), return false - if (TextUtils.isEmpty(ch) - || Character.codePointCount(ch, /* beginIndex= */ 0, ch.length()) != 1) { - return false; - } - - // Return true for the cases as below: - // 1. The character is in the TRIMMABLE_CHARACTERS set - // 2. The character is not supported in the system font - return TRIMMABLE_CHARACTERS.contains(ch) || !paint.hasGlyph(ch.toString()); - } - - /** - * If the {@code sequence} has some leading trimmable characters, creates a new copy - * and removes the trimmable characters from the copy. Otherwise the given - * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} - * to determine whether the character is trimmable or not. - * - * @return the trimmed string or the original string that has no - * leading trimmable characters. - * @see #isTrimmable(Paint, CharSequence) - * @see #trim(CharSequence) - * @see #trimEnd(CharSequence) - * - * @hide - */ - @VisibleForTesting - @NonNull - public static CharSequence trimStart(@NonNull CharSequence sequence) { - Objects.requireNonNull(sequence); - - if (TextUtils.isEmpty(sequence)) { - return sequence; - } - - final Paint paint = new Paint(); - int trimCount = 0; - final int[] codePoints = sequence.codePoints().toArray(); - for (int i = 0, length = codePoints.length; i < length; i++) { - String ch = Character.toString(codePoints[i]); - if (!isTrimmable(paint, ch)) { - break; - } - trimCount += ch.length(); - } - if (trimCount == 0) { - return sequence; - } - return sequence.subSequence(trimCount, sequence.length()); - } - - /** - * If the {@code sequence} has some trailing trimmable characters, creates a new copy - * and removes the trimmable characters from the copy. Otherwise the given - * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} - * to determine whether the character is trimmable or not. - * - * @return the trimmed sequence or the original sequence that has no - * trailing trimmable characters. - * @see #isTrimmable(Paint, CharSequence) - * @see #trimStart(CharSequence) - * @see #trim(CharSequence) - * - * @hide - */ - @VisibleForTesting - @NonNull - public static CharSequence trimEnd(@NonNull CharSequence sequence) { - Objects.requireNonNull(sequence); - - if (TextUtils.isEmpty(sequence)) { - return sequence; - } - - final Paint paint = new Paint(); - int trimCount = 0; - final int[] codePoints = sequence.codePoints().toArray(); - for (int i = codePoints.length - 1; i >= 0; i--) { - String ch = Character.toString(codePoints[i]); - if (!isTrimmable(paint, ch)) { - break; - } - trimCount += ch.length(); - } - - if (trimCount == 0) { - return sequence; - } - return sequence.subSequence(0, sequence.length() - trimCount); - } - - /** - * If the {@code sequence} has some leading or trailing trimmable characters, creates - * a new copy and removes the trimmable characters from the copy. Otherwise the given - * {@code sequence} is returned as it is. Use {@link #isTrimmable(Paint, CharSequence)} - * to determine whether the character is trimmable or not. - * - * @return the trimmed sequence or the original sequence that has no leading or - * trailing trimmable characters. - * @see #isTrimmable(Paint, CharSequence) - * @see #trimStart(CharSequence) - * @see #trimEnd(CharSequence) - * - * @hide - */ - @VisibleForTesting - @NonNull - public static CharSequence trim(@NonNull CharSequence sequence) { - Objects.requireNonNull(sequence); - - if (TextUtils.isEmpty(sequence)) { - return sequence; - } - - CharSequence result = trimStart(sequence); - if (TextUtils.isEmpty(result)) { - return result; - } - - return trimEnd(result); - } } diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 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/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/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/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/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/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/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/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 49a0bd3289aa..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; @@ -202,8 +203,8 @@ public final class SystemClock { * Returns nanoseconds since boot, not counting time spent in deep sleep. * * @return nanoseconds of non-sleep uptime since boot. - * @hide */ + @FlaggedApi(Flags.FLAG_ADPF_GPU_REPORT_ACTUAL_WORK_DURATION) @CriticalNative @android.ravenwood.annotation.RavenwoodReplace public static native long uptimeNanos(); diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java index 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/provider/Settings.java b/core/java/android/provider/Settings.java index 4e7734c5471d..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. * @@ -19472,6 +19463,7 @@ public final class Settings { * * @hide */ + @Readable public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package"; /** @@ -19479,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/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/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/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/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/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/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/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/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/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/content/pm/LauncherActivityInfoTest.java b/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java deleted file mode 100644 index e19c4b15d300..000000000000 --- a/core/tests/coretests/src/android/content/pm/LauncherActivityInfoTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for {@link android.content.pm.LauncherActivityInfo} - */ -@Presubmit -@RunWith(AndroidJUnit4.class) -public class LauncherActivityInfoTest { - - @Test - public void testTrimStart() { - // Invisible case - assertThat(LauncherActivityInfo.trimStart("\u0009").toString()).isEmpty(); - // It is not supported in the system font - assertThat(LauncherActivityInfo.trimStart("\u0FE1").toString()).isEmpty(); - // Surrogates case - assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36").toString()) - .isEqualTo("\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trimStart("\u0009\u0FE1\uD83E\uDD36A").toString()) - .isEqualTo("\uD83E\uDD36A"); - assertThat(LauncherActivityInfo.trimStart("\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\uD83E\uDD36A\u0009\u0FE1"); - assertThat(LauncherActivityInfo.trimStart("A\uD83E\uDD36\u0009\u0FE1A").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A"); - assertThat(LauncherActivityInfo.trimStart( - "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trimStart( - "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\uD83E\uDD36A\u0009\u0FE1"); - } - - @Test - public void testTrimEnd() { - // Invisible case - assertThat(LauncherActivityInfo.trimEnd("\u0009").toString()).isEmpty(); - // It is not supported in the system font - assertThat(LauncherActivityInfo.trimEnd("\u0FE1").toString()).isEmpty(); - // Surrogates case - assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36").toString()) - .isEqualTo("\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trimEnd("\u0009\u0FE1\uD83E\uDD36A").toString()) - .isEqualTo("\u0009\u0FE1\uD83E\uDD36A"); - assertThat(LauncherActivityInfo.trimEnd("\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\uD83E\uDD36A"); - assertThat(LauncherActivityInfo.trimEnd("A\uD83E\uDD36\u0009\u0FE1A").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A"); - assertThat(LauncherActivityInfo.trimEnd( - "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trimEnd( - "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\u0009\u0FE1\uD83E\uDD36A"); - } - - @Test - public void testTrim() { - // Invisible case - assertThat(LauncherActivityInfo.trim("\u0009").toString()).isEmpty(); - // It is not supported in the system font - assertThat(LauncherActivityInfo.trim("\u0FE1").toString()).isEmpty(); - // Surrogates case - assertThat(LauncherActivityInfo.trim("\uD83E\uDD36").toString()) - .isEqualTo("\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trim("\u0009\u0FE1\uD83E\uDD36A").toString()) - .isEqualTo("\uD83E\uDD36A"); - assertThat(LauncherActivityInfo.trim("\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\uD83E\uDD36A"); - assertThat(LauncherActivityInfo.trim("A\uD83E\uDD36\u0009\u0FE1A").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A"); - assertThat(LauncherActivityInfo.trim( - "A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36").toString()) - .isEqualTo("A\uD83E\uDD36\u0009\u0FE1A\uD83E\uDD36"); - assertThat(LauncherActivityInfo.trim( - "\u0009\u0FE1\uD83E\uDD36A\u0009\u0FE1").toString()) - .isEqualTo("\uD83E\uDD36A"); - } -} diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java index 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/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/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/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/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/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/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/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c0d83c4c08d5..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, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 80fd51643b98..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", 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 243751fafe5d..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,8 +70,10 @@ 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() 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/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/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/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/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 701f7e584872..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; @@ -366,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; @@ -594,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); @@ -603,7 +604,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsBindHandle.dispose(); } if (nic != null) { - nic.setOnLockScreen(true); final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind( nic, mAodIconsViewModel, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 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 7769dd9dc9ab..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,9 @@ 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> @@ -168,6 +172,7 @@ constructor( private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, broadcastDispatcher: BroadcastDispatcher, + mobileConnectionsRepository: MobileConnectionsRepository, ) : AuthenticationRepository { override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = @@ -192,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( @@ -212,9 +219,12 @@ 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, @@ -354,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 5eefbf5353d3..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 @@ -200,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. @@ -247,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/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 ff36839460be..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. */ @@ -128,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)) } @@ -145,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. @@ -183,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 @@ -220,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 b2b8049e3cff..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,19 @@ 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. @@ -123,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/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 1f2621d0c987..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; @@ -128,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; @@ -170,6 +173,7 @@ import javax.inject.Named; BiometricsModule.class, BiometricsDomainLayerModule.class, BouncerInteractorModule.class, + BouncerRepositoryModule.class, BouncerViewModule.class, ClipboardOverlayModule.class, ClockRegistryModule.class, @@ -183,6 +187,7 @@ import javax.inject.Named; DisableFlagsModule.class, DisplayModule.class, DreamModule.class, + EventLogModule.class, FalsingModule.class, FlagsModule.class, FlagDependenciesModule.class, @@ -190,6 +195,7 @@ import javax.inject.Named; KeyEventRepositoryModule.class, KeyboardModule.class, KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, MediaProjectionModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 298811baba6c..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 @@ -74,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/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 cd3ecb3090ce..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 = 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/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/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/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/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/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/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 68e16baab9b8..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,6 +29,7 @@ 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 @@ -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( @@ -103,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/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/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/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/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/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/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/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index f8bc0ee287e9..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 @@ -25,9 +25,7 @@ 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.CoreStartable import com.android.systemui.common.ui.ConfigurationState -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView @@ -39,21 +37,15 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.Notificatio import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData -import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor +import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.onConfigChanged -import com.android.systemui.util.asIndenting import com.android.systemui.util.kotlin.mapValuesNotNullTo import com.android.systemui.util.kotlin.stateFlow -import com.android.systemui.util.printCollection import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value -import dagger.Binds -import dagger.multibindings.ClassKey -import dagger.multibindings.IntoMap -import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job @@ -134,6 +126,7 @@ object NotificationIconContainerViewBinder { ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { + view.setUseIncreasedIconScale(true) launch { viewModel.icons.bindIcons( view, @@ -229,7 +222,6 @@ object NotificationIconContainerViewBinder { -> FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight) } - val failedBindings = mutableSetOf<String>() val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>() var prevIcons = NotificationIconsViewData() @@ -237,64 +229,111 @@ object NotificationIconContainerViewBinder { val iconsDiff = NotificationIconsViewData.computeDifference(iconsData, prevIcons) prevIcons = iconsData + // Lookup 1:1 group icon replacements val replacingIcons: ArrayMap<String, StatusBarIcon> = - iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, info) -> - boundViewsByNotifKey[info.notifKey]?.first?.statusBarIcon + iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, notifKey) -> + boundViewsByNotifKey[notifKey]?.first?.statusBarIcon + } + view.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() } - view.setReplacingIcons(replacingIcons) - - for (notifKey in iconsDiff.removed) { - failedBindings.remove(notifKey) - val (child, job) = boundViewsByNotifKey.remove(notifKey) ?: continue - view.removeView(child) - job.cancel() - } - val toAdd: Sequence<String> = - iconsDiff.added.asSequence().map { it.notifKey } + failedBindings - for ((idx, notifKey) in toAdd.withIndex()) { - val sbiv = viewStore.iconView(notifKey) - if (sbiv == null) { - failedBindings.add(notifKey) - continue + // 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) + }, + ) } - // The view might still be transiently added if it was just removed and added again - view.removeTransientView(sbiv) - view.addView(sbiv, idx) - boundViewsByNotifKey.remove(notifKey)?.second?.cancel() - boundViewsByNotifKey[notifKey] = - Pair( - sbiv, - launch { - launch { layoutParams.collect { sbiv.layoutParams = it } } - bindIcon(notifKey, sbiv) - }, - ) - } - notifyBindingFailures(failedBindings) + // 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) - view.setChangingViewPositions(true) + // Track the binding failures so that they appear in dumpsys. + notifyBindingFailures(failedBindings) - // Re-sort notification icons - val expectedChildren = - iconsData.visibleKeys.mapNotNull { boundViewsByNotifKey[it.notifKey]?.first } - val childCount = view.childCount - for (i in 0 until childCount) { - val actual = view.getChildAt(i) - val expected = expectedChildren[i] - if (actual === expected) { - continue + // 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? 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 c03a4a5f5aab..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 @@ -44,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( @@ -77,7 +83,8 @@ 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, ) } @@ -86,7 +93,7 @@ constructor( headsUpIconInteractor.isolatedNotification .combine(icons) { isolatedNotif, iconsViewData -> isolatedNotif?.let { - iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif } + iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif } } } .pairwise(initialValue = null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconsViewData.kt index 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 9ff416a02ee6..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 @@ -141,7 +142,11 @@ class PeekDeviceNotInUseSuppressor( } class PeekOldWhenSuppressor(private val systemClock: SystemClock) : - VisualInterruptionFilter(types = setOf(PEEK), reason = "has old `when`") { + VisualInterruptionFilter( + types = setOf(PEEK), + reason = "has old `when`", + uiEventId = HUN_SUPPRESSED_OLD_WHEN + ) { private fun whenAge(entry: NotificationEntry) = systemClock.currentTimeMillis() - entry.sbn.notification.`when` diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt index b44a36724bdb..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 @@ -52,6 +57,8 @@ class FullScreenIntentDecisionProvider( val logReason: String val shouldLog: Boolean val isWarning: Boolean + val uiEventId: UiEventEnum? + val eventLogData: EventLogData? } private enum class DecisionImpl( @@ -60,7 +67,9 @@ class FullScreenIntentDecisionProvider( override val wouldFsiWithoutDnd: Boolean = shouldFsi, val supersedesDnd: Boolean = false, override val shouldLog: Boolean = true, - override val isWarning: Boolean = false + override val isWarning: Boolean = false, + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null ) : Decision { NO_FSI_NO_FULL_SCREEN_INTENT( false, @@ -73,9 +82,17 @@ class FullScreenIntentDecisionProvider( NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR( false, "suppressive group alert behavior", - isWarning = true + isWarning = true, + uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR, + eventLogData = EventLogData("231322873", "groupAlertBehavior") + ), + NO_FSI_SUPPRESSIVE_BUBBLE_METADATA( + false, + "suppressive bubble metadata", + isWarning = true, + uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, + eventLogData = EventLogData("274759612", "bubbleMetadata") ), - NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false, "suppressive bubble metadata", isWarning = true), NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"), FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"), FSI_DEVICE_DREAMING(true, "device is dreaming"), @@ -84,7 +101,13 @@ 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", isWarning = true), + NO_FSI_NO_HUN_OR_KEYGUARD( + false, + "no HUN or keyguard", + isWarning = true, + uiEventId = FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD, + eventLogData = EventLogData("231322873", "no hun or keyguard") + ), NO_FSI_SUPPRESSED_BY_DND(false, "suppressed by DND", wouldFsiWithoutDnd = false), NO_FSI_SUPPRESSED_ONLY_BY_DND(false, "suppressed only by DND", wouldFsiWithoutDnd = true) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 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/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 c0a1a32b1401..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 @@ -20,12 +20,15 @@ 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 @@ -33,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 @@ -43,6 +47,7 @@ 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, @@ -52,14 +57,25 @@ constructor( private val powerManager: PowerManager, private val statusBarStateController: StatusBarStateController, private val systemClock: SystemClock, + private val uiEventLogger: UiEventLogger, private val userTracker: UserTracker, ) : VisualInterruptionDecisionProvider { + interface Loggable { + val uiEventId: UiEventEnum? + val eventLogData: EventLogData? + } + private class DecisionImpl( override val shouldInterrupt: Boolean, override val logReason: String ) : Decision - private data class LoggableDecision private constructor(val decision: DecisionImpl) { + private data class LoggableDecision + private constructor( + val decision: DecisionImpl, + override val uiEventId: UiEventEnum? = null, + override val eventLogData: EventLogData? = null + ) : Loggable { companion object { val unsuppressed = LoggableDecision(DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")) @@ -74,7 +90,9 @@ constructor( fun suppressed(suppressor: VisualInterruptionSuppressor) = LoggableDecision( - DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason) + DecisionImpl(shouldInterrupt = false, logReason = suppressor.reason), + uiEventId = suppressor.uiEventId, + eventLogData = suppressor.eventLogData ) } } @@ -82,7 +100,7 @@ constructor( private class FullScreenIntentDecisionImpl( val entry: NotificationEntry, private val fsiDecision: FullScreenIntentDecisionProvider.Decision - ) : FullScreenIntentDecision { + ) : FullScreenIntentDecision, Loggable { var hasBeenLogged = false override val shouldInterrupt @@ -99,6 +117,12 @@ constructor( val isWarning get() = fsiDecision.isWarning + + override val uiEventId + get() = fsiDecision.uiEventId + + override val eventLogData + get() = fsiDecision.eventLogData } private val fullScreenIntentDecisionProvider = @@ -147,23 +171,23 @@ 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) } @@ -214,9 +238,10 @@ constructor( private fun logDecision( type: VisualInterruptionType, entry: NotificationEntry, - loggable: LoggableDecision + loggableDecision: LoggableDecision ) { - logger.logDecision(type.name, entry, loggable.decision) + logger.logDecision(type.name, entry, loggableDecision.decision) + logEvents(entry, loggableDecision) } override fun makeUnloggedFullScreenIntentDecision( @@ -250,6 +275,14 @@ constructor( } logger.logFullScreenIntentDecision(decision.entry, decision, decision.isWarning) + logEvents(decision.entry, decision) + } + + private fun logEvents(entry: NotificationEntry, loggable: Loggable) { + loggable.uiEventId?.let { uiEventLogger.log(it, entry.sbn.uid, entry.sbn.packageName) } + loggable.eventLogData?.let { + eventLog.writeEvent(0x534e4554, it.number, entry.sbn.uid, it.description) + } } private fun checkSuppressInterruptions(entry: NotificationEntry) = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt index 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/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/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/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/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/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/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 1d8a664992e6..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; @@ -182,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, 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/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 87ab5b0d157f..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 @@ -157,8 +170,7 @@ class AuthenticationRepositoryTest : SysuiTestCase() { 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/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 7a9cb6cc18c2..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 @@ -366,16 +379,23 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(isAnimationEnabled).isTrue() } + private fun TestScope.switchToScene(toScene: SceneKey) { + val currentScene by collectLastValue(sceneInteractor.desiredScene) + val bouncerShown = currentScene?.key != SceneKey.Bouncer && toScene == SceneKey.Bouncer + val bouncerHidden = currentScene?.key == SceneKey.Bouncer && toScene != SceneKey.Bouncer + sceneInteractor.changeScene(SceneModel(toScene), "reason") + sceneInteractor.onSceneChanged(SceneModel(toScene), "reason") + if (bouncerShown) underTest.onShown() + if (bouncerHidden) underTest.onHidden() + runCurrent() + + assertThat(currentScene).isEqualTo(SceneModel(toScene)) + } + private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.deviceEntryRepository.setUnlocked(false) - sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason") - sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason") - - assertThat(collectLastValue(sceneInteractor.desiredScene).invoke()) - .isEqualTo(SceneModel(SceneKey.Bouncer)) - underTest.onShown() - runCurrent() + switchToScene(SceneKey.Bouncer) } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 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/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/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/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/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/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/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 e1581eade4ca..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 @@ -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 1064475c744c..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,6 +33,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro ambientDisplayConfiguration, batteryController, deviceProvisionedController, + eventLog, globalSettings, headsUpManager, keyguardNotificationVisibilityProvider, @@ -42,6 +43,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro powerManager, statusBarStateController, systemClock, + uiEventLogger, userTracker, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 5e811561682e..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 @@ -47,6 +47,7 @@ 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 @@ -63,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 @@ -100,6 +106,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected val ambientDisplayConfiguration = FakeAmbientDisplayConfiguration(context) protected val batteryController = FakeBatteryController(leakCheck) protected val deviceProvisionedController = FakeDeviceProvisionedController() + protected val eventLog = FakeEventLog() protected val flags: NotifPipelineFlags = mock() protected val globalSettings = FakeGlobalSettings().also { it.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_ON) } @@ -147,18 +154,21 @@ 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 @@ -167,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() + } } } @@ -174,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 @@ -219,33 +243,56 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { } @Test + fun testLogsHunOldWhen() { + assertNoEventsLogged() + + ensurePeekState() + val entry = buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) } + + // The old code logs the "old when" UiEvent unconditionally, so don't expect that it hasn't. + if (provider !is NotificationInterruptStateProviderWrapper) { + provider.makeUnloggedHeadsUpDecision(entry) + assertNoEventsLogged() + } + + provider.makeAndLogHeadsUpDecision(entry) + assertUiEventLogged(HUN_SUPPRESSED_OLD_WHEN, entry.sbn.uid, entry.sbn.packageName) + assertNoSystemEventLogged() + } + + @Test fun testShouldPeek_oldWhen_now() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(0) }) + assertNoEventsLogged() } @Test fun testShouldPeek_oldWhen_notOldEnough() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS - 1) }) + assertNoEventsLogged() } @Test fun testShouldPeek_oldWhen_zeroWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = 0L }) + assertNoEventsLogged() } @Test fun testShouldPeek_oldWhen_negativeWhen() { ensurePeekState() assertShouldHeadsUp(buildPeekEntry { whenMs = -1L }) + assertNoEventsLogged() } @Test fun testShouldPeek_oldWhen_fullScreenIntent() { ensurePeekState() assertShouldHeadsUp(buildFsiEntry { whenMs = whenAgo(MAX_HUN_WHEN_AGE_MS) }) + assertNoEventsLogged() } @Test @@ -257,6 +304,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { isForegroundService = true } ) + assertNoEventsLogged() } @Test @@ -268,18 +316,21 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { isUserInitiatedJob = true } ) + assertNoEventsLogged() } @Test fun testShouldNotPeek_hiddenOnKeyguard() { ensurePeekState({ keyguardShouldHideNotification = true }) assertShouldNotHeadsUp(buildPeekEntry()) + assertNoEventsLogged() } @Test fun testShouldPeek_defaultLegacySuppressor() { ensurePeekState() withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -288,6 +339,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -296,6 +348,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test @@ -304,24 +357,28 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldNotHeadsUp(buildPeekEntry()) } + assertNoEventsLogged() } @Test fun testShouldPulse() { ensurePulseState() assertShouldHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPulse_disabled() { ensurePulseState { pulseOnNotificationsEnabled = false } assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test fun testShouldNotPulse_batterySaver() { ensurePulseState { isAodPowerSave = true } assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test @@ -330,30 +387,35 @@ 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() } @Test fun testShouldNotPulse_hiddenOnKeyguard() { ensurePulseState({ keyguardShouldHideNotification = true }) assertShouldNotHeadsUp(buildPulseEntry()) + assertNoEventsLogged() } @Test fun testShouldPulse_defaultLegacySuppressor() { ensurePulseState() withLegacySuppressor(neverSuppresses) { assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test @@ -362,6 +424,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldNotHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test @@ -370,6 +433,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } @Test @@ -378,6 +442,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldHeadsUp(buildPulseEntry()) } + assertNoEventsLogged() } private fun withPeekAndPulseEntry( @@ -399,6 +464,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_SUMMARY }) { assertShouldNotHeadsUp(it) + assertNoEventsLogged() } } @@ -410,6 +476,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_CHILDREN }) { assertShouldHeadsUp(it) + assertNoEventsLogged() } } @@ -421,24 +488,30 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_SUMMARY }) { assertShouldHeadsUp(it) + assertNoEventsLogged() } } @Test fun testShouldNotHeadsUp_justLaunchedFsi() { - withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { assertShouldNotHeadsUp(it) } + withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) { + assertShouldNotHeadsUp(it) + assertNoEventsLogged() + } } @Test fun testShouldBubble_withIntentAndIcon() { ensureBubbleState() assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = false }) + assertNoEventsLogged() } @Test fun testShouldBubble_withShortcut() { ensureBubbleState() assertShouldBubble(buildBubbleEntry { bubbleIsShortcut = true }) + assertNoEventsLogged() } @Test @@ -451,6 +524,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_SUMMARY } ) + assertNoEventsLogged() } @Test @@ -462,24 +536,28 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { hasBubbleMetadata = false } ) + assertNoEventsLogged() } @Test fun testShouldNotBubble_missingBubbleMetadata() { ensureBubbleState() assertShouldNotBubble(buildBubbleEntry { hasBubbleMetadata = false }) + assertNoEventsLogged() } @Test fun testShouldNotBubble_notAllowedToBubble() { ensureBubbleState() assertShouldNotBubble(buildBubbleEntry { canBubble = false }) + assertNoEventsLogged() } @Test fun testShouldBubble_defaultLegacySuppressor() { ensureBubbleState() withLegacySuppressor(neverSuppresses) { assertShouldBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -488,6 +566,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldNotBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -496,6 +575,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldNotBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test @@ -504,17 +584,22 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldBubble(buildBubbleEntry()) } + assertNoEventsLogged() } @Test fun testShouldNotBubble_hiddenOnKeyguard() { ensureBubbleState({ keyguardShouldHideNotification = true }) assertShouldNotBubble(buildBubbleEntry()) + assertNoEventsLogged() } @Test fun testShouldNotFsi_noFullScreenIntent() { - forEachFsiState { assertShouldNotFsi(buildFsiEntry { hasFsi = false }) } + forEachFsiState { + assertShouldNotFsi(buildFsiEntry { hasFsi = false }) + assertNoEventsLogged() + } } @Test @@ -526,6 +611,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { isStickyAndNotDemoted = true } ) + assertNoEventsLogged() } } @@ -536,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 @@ -554,6 +644,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { }, expectWouldInterruptWithoutDnd = false ) + assertNoEventsLogged() } } @@ -571,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( @@ -580,6 +692,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { groupAlertBehavior = GROUP_ALERT_CHILDREN } ) + assertNoEventsLogged() } } @@ -609,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 @@ -636,6 +775,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { forEachPeekableFsiState { ensurePeekState() assertShouldNotFsi(buildFsiEntry()) + assertNoEventsLogged() } } @@ -644,6 +784,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { forEachPeekableFsiState { ensurePeekState { hunSnoozed = true } assertShouldNotFsi(buildFsiEntry()) + assertNoEventsLogged() } } @@ -651,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 @@ -672,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() } } @@ -682,6 +840,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldFsi_suppressInterruptions() { forEachFsiState { withLegacySuppressor(alwaysSuppressesInterruptions) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -691,6 +850,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { withLegacySuppressor(alwaysSuppressesAwakeInterruptions) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -698,6 +858,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { fun testShouldFsi_suppressAwakeHeadsUp() { forEachFsiState { withLegacySuppressor(alwaysSuppressesAwakeHeadsUp) { assertShouldFsi(buildFsiEntry()) } + assertNoEventsLogged() } } @@ -1080,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/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/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 af1930ef143e..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,8 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 + override val minPasswordLength: Int = 4 + private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false) override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = _isPinEnhancedPrivacyEnabled.asStateFlow() @@ -178,6 +180,7 @@ 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/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/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index 3674244926e8..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 && 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/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/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 562fe3b17f9f..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 @@ -692,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); } @@ -710,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); @@ -776,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); @@ -954,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 @@ -987,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/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 4b004340f923..7e4cf4f35132 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -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/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/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/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/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 2ec3700229b2..8c753671d77d 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"); 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/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/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index ea783b88cf64..b281808e89b6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -758,6 +758,11 @@ 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/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/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/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/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/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/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/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/proguard.flags b/services/proguard.flags index 407505d6eda0..88561b460b05 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -45,10 +45,6 @@ public static void write(...); } -# Binder interfaces --keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface --keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface - # Various classes subclassed in or referenced via JNI in ethernet-service -keep public class android.net.** { *; } -keep,allowoptimization,allowaccessmodification class com.android.net.module.util.* { *; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 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/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/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 f978990f0fac..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 @@ -1940,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()) 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/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/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/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/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/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/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/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/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/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/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 |